mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50035643f7 | ||
|
|
64c22f9075 | ||
|
|
4895a29e92 | ||
|
|
6cdb3179c8 | ||
|
|
4fd52af682 | ||
|
|
5a0bad130d | ||
|
|
08d025968e | ||
|
|
1cd0e94c26 | ||
|
|
1ec976f95e | ||
|
|
f10ef5f331 | ||
|
|
653bc59813 | ||
|
|
3e991e13dc | ||
|
|
5397b53e04 | ||
|
|
f09259f6de | ||
|
|
430a49ebc2 | ||
|
|
1091a8ba1a | ||
|
|
9116b6c8cd | ||
|
|
990e34a129 | ||
|
|
472d7b9376 | ||
|
|
50dd7881b1 | ||
|
|
aa8f8db305 | ||
|
|
cdd9b1e1eb | ||
|
|
2d2ce70edc | ||
|
|
78f1d1df38 | ||
|
|
a06bb3f992 | ||
|
|
e289f30e5d | ||
|
|
4b10993970 | ||
|
|
d78a870e66 | ||
|
|
cb41c58f21 | ||
|
|
c55d97183d | ||
|
|
8646cbc9f5 | ||
|
|
64c1d4c5bb | ||
|
|
76b949c196 | ||
|
|
c93ef47b9b | ||
|
|
2b414cd825 | ||
|
|
ac42447459 | ||
|
|
401d20fa1f | ||
|
|
6b5b570b45 | ||
|
|
9e6f666616 | ||
|
|
8ef9fe44c7 | ||
|
|
3e0a544eb8 | ||
|
|
6474891f1e | ||
|
|
40abb526cc | ||
|
|
c532d910c0 | ||
|
|
44288f6280 | ||
|
|
eec84ade86 | ||
|
|
10419ff441 | ||
|
|
57981b533d | ||
|
|
c5eaa0ab69 | ||
|
|
2c01e9e747 | ||
|
|
4c39b3188c | ||
|
|
224872ce03 | ||
|
|
f10a984ba3 | ||
|
|
c7f7181388 | ||
|
|
92530299eb | ||
|
|
58cd23d1ac | ||
|
|
5d85c10a2c | ||
|
|
f11270dac0 | ||
|
|
bcd76d4718 | ||
|
|
4dd2b66aaa | ||
|
|
829a140d9b | ||
|
|
f04fa5d54f | ||
|
|
b4175c1454 | ||
|
|
0728105b2b | ||
|
|
ea09d1d10e | ||
|
|
e7f88588bf | ||
|
|
ce890c3455 | ||
|
|
cbba00d360 | ||
|
|
bf366cb99d | ||
|
|
142d55ff24 | ||
|
|
a58400a82c | ||
|
|
aac28d24ac | ||
|
|
d624a3e648 | ||
|
|
c57294b41a | ||
|
|
485e82dcb1 | ||
|
|
e8d910ffd3 | ||
|
|
ab1f636960 | ||
|
|
52c4670237 | ||
|
|
2ae12fb0a0 | ||
|
|
6c1d3c4fd8 | ||
|
|
08de2b3fc4 | ||
|
|
945a9e3122 | ||
|
|
6e28ba8927 | ||
|
|
7369551c02 | ||
|
|
e22c76b3ac | ||
|
|
a438091266 | ||
|
|
85baf79d17 | ||
|
|
1c68be0631 | ||
|
|
b222dde835 | ||
|
|
78e5a395da | ||
|
|
7846e6a31e | ||
|
|
b7e7755edd | ||
|
|
40f48cbeb4 | ||
|
|
fb8313aa97 | ||
|
|
f9643917d3 | ||
|
|
ac1c9b1218 | ||
|
|
daf0d6b9a7 | ||
|
|
fa14863284 | ||
|
|
cd9ec0afc7 | ||
|
|
f3f6f1008a | ||
|
|
0e52672f4a | ||
|
|
bc1dc1fd63 | ||
|
|
f93f2c158b | ||
|
|
9229e2d88b | ||
|
|
51bb6b751e | ||
|
|
2cc87cab65 | ||
|
|
7cd273ae58 | ||
|
|
001fb7ef60 | ||
|
|
cf4660841a | ||
|
|
a52f755ce8 | ||
|
|
2a2abc4843 | ||
|
|
8af222c1e3 | ||
|
|
d3e6949007 | ||
|
|
a033b25ecb | ||
|
|
39dd30c239 | ||
|
|
c5c99d2357 | ||
|
|
25ff30ee8c | ||
|
|
6b250a74f8 | ||
|
|
5331397c6c | ||
|
|
0b39024133 | ||
|
|
438d286fd5 | ||
|
|
18a61f1058 | ||
|
|
20d012ae3f | ||
|
|
efc54ab8ba |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -20,5 +20,3 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
/CHANGELOG.md merge=union
|
||||
|
||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,7 +1,4 @@
|
||||
- [ ] Tested on all platforms changed
|
||||
- [ ] Compilation warnings were addressed
|
||||
- [ ] `cargo fmt` has been run on this branch
|
||||
- [ ] `cargo doc` builds successfully
|
||||
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
|
||||
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
||||
- [ ] Created or updated an example program if it would help users understand this functionality
|
||||
|
||||
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@@ -2,16 +2,8 @@ name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**.toml'
|
||||
- '.github/workflows/ci.yml'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**.toml'
|
||||
- '.github/workflows/ci.yml'
|
||||
|
||||
jobs:
|
||||
Check_Formatting:
|
||||
@@ -25,12 +17,14 @@ jobs:
|
||||
- name: Check Formatting
|
||||
run: cargo +stable fmt --all -- --check
|
||||
|
||||
Tests:
|
||||
tests:
|
||||
name: Tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust_version: [stable, nightly]
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: i686-pc-windows-msvc, os: windows-latest, }
|
||||
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
||||
@@ -50,10 +44,12 @@ jobs:
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0"
|
||||
PKG_CONFIG_ALLOW_CROSS: 1
|
||||
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
|
||||
OPTIONS: ${{ matrix.platform.options }}
|
||||
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
@@ -69,18 +65,26 @@ jobs:
|
||||
with:
|
||||
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
|
||||
targets: ${{ matrix.platform.target }}
|
||||
components: clippy
|
||||
|
||||
- name: Setup NDK path
|
||||
shell: bash
|
||||
# "Temporary" workaround until https://github.com/actions/virtual-environments/issues/5879#issuecomment-1195156618
|
||||
# gets looked into.
|
||||
run: echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> $GITHUB_ENV
|
||||
- name: Install Linux dependencies
|
||||
if: (matrix.platform.os == 'ubuntu-latest')
|
||||
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libfreetype6-dev:i386 libfontconfig1-dev:i386
|
||||
- name: Install cargo-apk
|
||||
if: contains(matrix.platform.target, 'android')
|
||||
run: cargo install cargo-apk
|
||||
|
||||
- name: Check documentation
|
||||
shell: bash
|
||||
if: matrix.platform.target != 'wasm32-unknown-unknown'
|
||||
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
@@ -97,6 +101,10 @@ jobs:
|
||||
!contains(matrix.platform.target, 'wasm32'))
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Lint with clippy
|
||||
shell: bash
|
||||
if: (matrix.rust_version != 'nightly') && !contains(matrix.platform.options, '--no-default-features')
|
||||
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
|
||||
|
||||
- name: Build with serde enabled
|
||||
shell: bash
|
||||
|
||||
111
CHANGELOG.md
111
CHANGELOG.md
@@ -1,6 +1,97 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
|
||||
|
||||
And please only add new entries to the top of this list, right below the `# Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.27.0 (2022-07-26)
|
||||
|
||||
- On Windows, fix hiding a maximized window.
|
||||
- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`.
|
||||
- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception.
|
||||
- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11.
|
||||
- On X11, fix events for caps lock key not being sent
|
||||
- Build docs on `docs.rs` for iOS and Android as well.
|
||||
- **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`.
|
||||
- Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute.
|
||||
- On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q.
|
||||
- On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning.
|
||||
- On Wayland, fix bug where the cursor wouldn't hide in GNOME.
|
||||
- On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events.
|
||||
- On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`.
|
||||
- On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`.
|
||||
- On Windows, remove internally unique DC per window.
|
||||
- On macOS, remove the need to call `set_ime_position` after moving the window.
|
||||
- Added `Window::is_visible`.
|
||||
- Added `Window::is_resizable`.
|
||||
- Added `Window::is_decorated`.
|
||||
- On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait`
|
||||
- On X11, fix scale factor calculation when the only monitor is reconnected
|
||||
- On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`.
|
||||
- On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends.
|
||||
- **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`.
|
||||
- Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable.
|
||||
- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
|
||||
- Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it.
|
||||
- Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead.
|
||||
- **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods).
|
||||
- **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods).
|
||||
- **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods).
|
||||
- **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies.
|
||||
- The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings.
|
||||
- On Wayland, fix polling during consecutive `EventLoop::run_return` invocations.
|
||||
- On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen`
|
||||
- On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found.
|
||||
- Added helper methods on `ControlFlow` to set its value.
|
||||
- On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor
|
||||
sent a cancel or frame event.
|
||||
- On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms.
|
||||
- **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`.
|
||||
- On Wayland, fallback CSD was replaced with proper one:
|
||||
- `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder.
|
||||
- `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window.
|
||||
- `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting.
|
||||
- `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library.
|
||||
- `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering.
|
||||
- On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`.
|
||||
- On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages
|
||||
- **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms.
|
||||
- Added `Window::set_ime_allowed` supported on desktop platforms.
|
||||
- **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled.
|
||||
- On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`.
|
||||
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
|
||||
- Implemented `Default` on `EventLoop<()>`.
|
||||
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
|
||||
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
|
||||
- On Wayland, add support for `Window::set_cursor_position`.
|
||||
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
|
||||
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
|
||||
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
|
||||
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
|
||||
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision.
|
||||
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
|
||||
- On Windows, fix focus events being sent to inactive windows.
|
||||
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
|
||||
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
|
||||
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
|
||||
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
|
||||
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
|
||||
|
||||
# 0.26.1 (2022-01-05)
|
||||
|
||||
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.
|
||||
- On Web, implement cursor grabbing through the pointer lock API.
|
||||
- On X11, add mappings for numpad comma, numpad enter, numlock and pause.
|
||||
- On macOS, fix Pinyin IME input by reverting a change that intended to improve IME.
|
||||
- On Windows, fix a crash with transparent windows on Windows 11.
|
||||
|
||||
# 0.26.0 (2021-12-01)
|
||||
|
||||
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74).
|
||||
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading.
|
||||
- On X11, bump `mio` to 0.8.
|
||||
- On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`.
|
||||
- On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0.
|
||||
@@ -213,13 +304,13 @@
|
||||
|
||||
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
|
||||
- **Breaking**: Overhaul how Winit handles DPI:
|
||||
+ Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
|
||||
+ Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
|
||||
+ `hidpi_factor` has been renamed to `scale_factor`.
|
||||
+ `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
|
||||
- Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
|
||||
- Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
|
||||
- `hidpi_factor` has been renamed to `scale_factor`.
|
||||
- `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
|
||||
resizes the window in response to the change.
|
||||
+ On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
|
||||
+ `Size` and `Position` types are now generic over their exact pixel type.
|
||||
- On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
|
||||
- `Size` and `Position` types are now generic over their exact pixel type.
|
||||
|
||||
# 0.20.0 Alpha 6 (2020-01-03)
|
||||
|
||||
@@ -324,7 +415,7 @@
|
||||
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
|
||||
consists of `Fullscreen::Exclusive(VideoMode)` and
|
||||
`Fullscreen::Borderless(MonitorHandle)` variants.
|
||||
- Adds support for exclusive fullscreen mode.
|
||||
- Adds support for exclusive fullscreen mode.
|
||||
- On iOS, add support for hiding the home indicator.
|
||||
- On iOS, add support for deferring system gestures.
|
||||
- On iOS, fix a crash that occurred while acquiring a monitor's name.
|
||||
@@ -523,7 +614,7 @@ and `WindowEvent::HoveredFile`.
|
||||
# Version 0.16.1 (2018-07-02)
|
||||
|
||||
- Added logging through `log`. Logging will become more extensive over time.
|
||||
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
|
||||
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This _greatly_ cuts back on unsightly auto-resizing that would occur immediately after window creation.
|
||||
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
|
||||
|
||||
# Version 0.16.0 (2018-06-25)
|
||||
@@ -673,7 +764,7 @@ and `WindowEvent::HoveredFile`.
|
||||
|
||||
# Version 0.10.1 (2018-02-05)
|
||||
|
||||
*Yanked*
|
||||
_Yanked_
|
||||
|
||||
# Version 0.10.0 (2017-12-27)
|
||||
|
||||
|
||||
@@ -25,7 +25,10 @@ When making a code contribution to winit, before opening your pull request, plea
|
||||
- you updated any relevant documentation in winit
|
||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
||||
maintainers and future contributors don't have to try to guess what your code is supposed to do
|
||||
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
|
||||
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
|
||||
|
||||
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
|
||||
handle those for you when merging (see below).
|
||||
- if your PR affects the platform compatibility of one or more features or adds another feature, the
|
||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||
should be updated.
|
||||
@@ -34,9 +37,26 @@ Once your PR is open, you can ask for review by a maintainer of your platform. W
|
||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
||||
|
||||
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
|
||||
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
|
||||
"give contributors write access to the branch" checkbox when creating the PR.
|
||||
|
||||
## Maintainers & Testers
|
||||
|
||||
The current [list of testers and contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors)
|
||||
can be found on the Wiki.
|
||||
|
||||
If you are interested in contributing or testing on a platform, please add yourself to that table!
|
||||
|
||||
## Making a new release
|
||||
|
||||
If you believe a new release is warranted, you can make a pull-request with:
|
||||
- An updated version number (remember to change the version everywhere it is used).
|
||||
- A new section in the changelog (below the `# Unreleased` section).
|
||||
|
||||
This gives contributors an opportunity to squeeze in an extra PR or two that they feel is valuable
|
||||
enough to warrant blocking the release a little.
|
||||
|
||||
Once the PR is merged, a maintainer will create a new tag matching the version name (e.g. `v0.26.1`),
|
||||
and a CI job will automatically release the new version. Remember that the release date in the
|
||||
changelog must be kept in check with the actual release date.
|
||||
|
||||
112
Cargo.toml
112
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.26.0"
|
||||
version = "0.27.0"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
@@ -14,31 +14,49 @@ categories = ["gui"]
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde"]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"]
|
||||
# These are all tested in CI
|
||||
targets = [
|
||||
# Windows
|
||||
"i686-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc",
|
||||
# macOS
|
||||
"x86_64-apple-darwin",
|
||||
# Unix (X11 & Wayland)
|
||||
"i686-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
# iOS
|
||||
"x86_64-apple-ios",
|
||||
# Android
|
||||
"aarch64-linux-android",
|
||||
# WebAssembly
|
||||
"wasm32-unknown-unknown",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen"]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
|
||||
wayland = ["wayland-client", "wayland-protocols", "sctk"]
|
||||
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/title"]
|
||||
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
||||
|
||||
[dependencies]
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
lazy_static = "1"
|
||||
once_cell = "1.12"
|
||||
log = "0.4"
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
raw-window-handle = "0.4.2"
|
||||
raw-window-handle = "0.5.0"
|
||||
bitflags = "1"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.23.12"
|
||||
simple_logger = "1.9"
|
||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||
simple_logger = "2.1.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk = "0.5"
|
||||
ndk-sys = "0.2.0"
|
||||
ndk-glue = "0.5"
|
||||
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
|
||||
ndk = "0.7.0"
|
||||
ndk-glue = "0.7.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
objc = "0.2.7"
|
||||
@@ -48,51 +66,48 @@ cocoa = "0.24"
|
||||
core-foundation = "0.9"
|
||||
core-graphics = "0.22"
|
||||
dispatch = "0.2.0"
|
||||
block = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
version = "0.1.4"
|
||||
default_features = false
|
||||
features = ["display_link"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
parking_lot = "0.11"
|
||||
parking_lot = "0.12"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||
version = "0.3.9"
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
version = "0.36"
|
||||
features = [
|
||||
"combaseapi",
|
||||
"commctrl",
|
||||
"dwmapi",
|
||||
"errhandlingapi",
|
||||
"imm",
|
||||
"hidusage",
|
||||
"libloaderapi",
|
||||
"objbase",
|
||||
"ole2",
|
||||
"processthreadsapi",
|
||||
"shellapi",
|
||||
"shellscalingapi",
|
||||
"shobjidl_core",
|
||||
"unknwnbase",
|
||||
"winbase",
|
||||
"windowsx",
|
||||
"winerror",
|
||||
"wingdi",
|
||||
"winnt",
|
||||
"winuser",
|
||||
"mmsystem",
|
||||
"timeapi"
|
||||
"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_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",
|
||||
]
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.29", default_features = false, features = ["use_system_lib"], optional = true }
|
||||
wayland-protocols = { version = "0.29", features = [ "staging_protocols"], optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.15.1", default_features = false, features = ["calloop"], optional = true }
|
||||
wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true }
|
||||
wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.4.1", optional = true }
|
||||
mio = { version = "0.8", features = ["os-ext"], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
parking_lot = { version = "0.11.0", optional = true }
|
||||
parking_lot = { version = "0.12.0", optional = true }
|
||||
libc = "0.2.64"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
||||
@@ -126,3 +141,8 @@ version = "0.2.45"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
console_log = "0.2"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"run-wasm",
|
||||
]
|
||||
|
||||
12
FEATURES.md
12
FEATURES.md
@@ -100,8 +100,10 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
### Input Handling
|
||||
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
|
||||
- **Mouse set location**: Forcibly changing the location of the pointer.
|
||||
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
|
||||
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
||||
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
||||
- **Cursor hittest**: Handle or ignore mouse events for a window.
|
||||
- **Touch events**: Single-touch events.
|
||||
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
||||
@@ -172,7 +174,7 @@ Legend:
|
||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|
||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|
||||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|
||||
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**|
|
||||
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
||||
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|
||||
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
||||
@@ -196,9 +198,11 @@ Legend:
|
||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|
||||
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ |
|
||||
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|
||||
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
||||
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|
||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ |
|
||||
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|
||||
|
||||
45
README.md
45
README.md
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.26.0"
|
||||
winit = "0.27.0"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -19,9 +19,8 @@ For features _outside_ the scope of winit, see [Missing features provided by oth
|
||||
|
||||
Join us in any of these:
|
||||
|
||||
[](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
|
||||
[](https://matrix.to/#/#Glutin:matrix.org)
|
||||
[](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://matrix.to/#/#rust-windowing:matrix.org)
|
||||
[](https://web.libera.chat/#winit)
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -70,8 +69,18 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
#### Wayland
|
||||
|
||||
Note that windows don't appear on Wayland until you draw/present to them.
|
||||
|
||||
`winit` doesn't do drawing, try the examples in [`glutin`] instead.
|
||||
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
|
||||
#### WebAssembly
|
||||
|
||||
To run the web example: `cargo run-wasm --example web`
|
||||
|
||||
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
|
||||
|
||||
On the web platform, a Winit window is backed by a `<canvas>` element. You can
|
||||
@@ -92,15 +101,16 @@ book].
|
||||
|
||||
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
|
||||
|
||||
The `ndk_glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk_glue`'s internal NativeActivity static is not the same due to version mismatch.
|
||||
The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
|
||||
|
||||
`ndk_glue` <-> `winit` version comparison compatibility:
|
||||
`winit` compatibility table with `ndk-glue`:
|
||||
|
||||
| winit | ndk_glue |
|
||||
| winit | ndk-glue |
|
||||
| :---: | :------------------: |
|
||||
| 0.24 | `ndk_glue = "0.2.0"` |
|
||||
| 0.25 | `ndk_glue = "0.3.0"` |
|
||||
| 0.26 | `ndk_glue = "0.5.0"` |
|
||||
| 0.24 | `ndk-glue = "0.2.0"` |
|
||||
| 0.25 | `ndk-glue = "0.3.0"` |
|
||||
| 0.26 | `ndk-glue = "0.5.0"` |
|
||||
| 0.27 | `ndk-glue = "0.7.0"` |
|
||||
|
||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
||||
|
||||
@@ -122,10 +132,13 @@ And run the application with `cargo apk run --example request_redraw_threaded`
|
||||
|
||||
#### MacOS
|
||||
|
||||
To ensure compatibility with older MacOS systems, winit links to
|
||||
CGDisplayCreateUUIDFromDisplayID through the CoreGraphics framework.
|
||||
However, under certain setups this function is only available to be linked
|
||||
through the newer ColorSync framework. So, winit provides the
|
||||
`WINIT_LINK_COLORSYNC` environment variable which can be set to `1` or `true`
|
||||
while compiling to enable linking via ColorSync.
|
||||
A lot of functionality expects the application to be ready before you start
|
||||
doing anything; this includes creating windows, fetching monitors, drawing,
|
||||
and so on, see issues [#2238], [#2051] and [#2087].
|
||||
|
||||
If you encounter problems, you should try doing your initialization inside
|
||||
`Event::NewEvents(StartCause::Init)`.
|
||||
|
||||
[#2238]: https://github.com/rust-windowing/winit/issues/2238
|
||||
[#2051]: https://github.com/rust-windowing/winit/issues/2051
|
||||
[#2087]: https://github.com/rust-windowing/winit/issues/2087
|
||||
|
||||
10
build.rs
10
build.rs
@@ -1,10 +0,0 @@
|
||||
fn main() {
|
||||
// If building for macos and WINIT_LINK_COLORSYNC is set to true
|
||||
// use CGDisplayCreateUUIDFromDisplayID from ColorSync instead of CoreGraphics
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").map_or(false, |os| os == "macos")
|
||||
&& std::env::var("WINIT_LINK_COLORSYNC")
|
||||
.map_or(false, |v| v == "1" || v.eq_ignore_ascii_case("true"))
|
||||
{
|
||||
println!("cargo:rustc-cfg=use_colorsync_cgdisplaycreateuuidfromdisplayid");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::{thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -88,23 +90,21 @@ fn main() {
|
||||
window.request_redraw();
|
||||
}
|
||||
if close_requested {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {}
|
||||
Event::RedrawEventsCleared => {
|
||||
*control_flow = match mode {
|
||||
Mode::Wait => ControlFlow::Wait,
|
||||
match mode {
|
||||
Mode::Wait => control_flow.set_wait(),
|
||||
Mode::WaitUntil => {
|
||||
if wait_cancelled {
|
||||
*control_flow
|
||||
} else {
|
||||
ControlFlow::WaitUntil(instant::Instant::now() + WAIT_TIME)
|
||||
if !wait_cancelled {
|
||||
control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
|
||||
}
|
||||
}
|
||||
Mode::Poll => {
|
||||
thread::sleep(POLL_SLEEP_TIME);
|
||||
ControlFlow::Poll
|
||||
control_flow.set_poll();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -15,7 +17,7 @@ fn main() {
|
||||
let mut cursor_idx = 0;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
@@ -42,8 +44,7 @@ fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
event_loop::EventLoop,
|
||||
window::{CursorGrabMode, WindowBuilder},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
@@ -17,11 +19,11 @@ fn main() {
|
||||
let mut modifiers = ModifiersState::default();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
@@ -32,11 +34,23 @@ fn main() {
|
||||
..
|
||||
} => {
|
||||
use winit::event::VirtualKeyCode::*;
|
||||
match key {
|
||||
Escape => *control_flow = ControlFlow::Exit,
|
||||
G => window.set_cursor_grab(!modifiers.shift()).unwrap(),
|
||||
H => window.set_cursor_visible(modifiers.shift()),
|
||||
_ => (),
|
||||
let result = match key {
|
||||
Escape => {
|
||||
control_flow.set_exit();
|
||||
Ok(())
|
||||
}
|
||||
G => window.set_cursor_grab(CursorGrabMode::Confined),
|
||||
L => window.set_cursor_grab(CursorGrabMode::Locked),
|
||||
A => window.set_cursor_grab(CursorGrabMode::None),
|
||||
H => {
|
||||
window.set_cursor_visible(modifiers.shift());
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(m) => modifiers = m,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoopBuilder,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -13,7 +15,7 @@ fn main() {
|
||||
}
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event();
|
||||
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
@@ -34,14 +36,14 @@ fn main() {
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::UserEvent(event) => println!("user event: {:?}", event),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
} => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{
|
||||
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
|
||||
},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
@@ -22,7 +24,7 @@ fn main() {
|
||||
eprintln!("Switch which window is to be dragged by pressing \"x\".")
|
||||
}
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
button: MouseButton::Left,
|
||||
|
||||
@@ -1,69 +1,107 @@
|
||||
use std::io::{stdin, stdout, Write};
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::monitor::{MonitorHandle, VideoMode};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
|
||||
let fullscreen = Some(match num {
|
||||
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
|
||||
2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))),
|
||||
_ => panic!("Please enter a valid number"),
|
||||
});
|
||||
|
||||
let mut decorations = true;
|
||||
let mut minimized = false;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(fullscreen.clone())
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
let mut monitor_index = 0;
|
||||
let mut monitor = event_loop
|
||||
.available_monitors()
|
||||
.next()
|
||||
.expect("no monitor found!");
|
||||
println!("Monitor: {:?}", monitor.name());
|
||||
|
||||
let mut mode_index = 0;
|
||||
let mut mode = monitor.video_modes().next().expect("no mode found");
|
||||
println!("Mode: {}", mode);
|
||||
|
||||
println!("Keys:");
|
||||
println!("- Esc\tExit");
|
||||
println!("- F\tToggle exclusive fullscreen mode");
|
||||
println!("- B\tToggle borderless mode");
|
||||
println!("- S\tNext screen");
|
||||
println!("- M\tNext mode for this screen");
|
||||
println!("- D\tToggle window decorations");
|
||||
println!("- X\tMaximize window");
|
||||
println!("- Z\tMinimize window");
|
||||
|
||||
event_loop.run(move |event, elwt, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match (virtual_code, state) {
|
||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} => match virtual_code {
|
||||
VirtualKeyCode::Escape => control_flow.set_exit(),
|
||||
VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => {
|
||||
window.set_fullscreen(None);
|
||||
}
|
||||
VirtualKeyCode::F => {
|
||||
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
|
||||
println!("Setting mode: {fullscreen:?}");
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
VirtualKeyCode::B => {
|
||||
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
|
||||
println!("Setting mode: {fullscreen:?}");
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
VirtualKeyCode::S => {
|
||||
monitor_index += 1;
|
||||
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
|
||||
monitor = mon;
|
||||
} else {
|
||||
window.set_fullscreen(fullscreen.clone());
|
||||
monitor_index = 0;
|
||||
monitor = elwt.available_monitors().next().expect("no monitor found!");
|
||||
}
|
||||
println!("Monitor: {:?}", monitor.name());
|
||||
|
||||
mode_index = 0;
|
||||
mode = monitor.video_modes().next().expect("no mode found");
|
||||
println!("Mode: {}", mode);
|
||||
}
|
||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||
println!("window.fullscreen {:?}", window.fullscreen());
|
||||
VirtualKeyCode::M => {
|
||||
mode_index += 1;
|
||||
if let Some(m) = monitor.video_modes().nth(mode_index) {
|
||||
mode = m;
|
||||
} else {
|
||||
mode_index = 0;
|
||||
mode = monitor.video_modes().next().expect("no mode found");
|
||||
}
|
||||
println!("Mode: {}", mode);
|
||||
}
|
||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||
VirtualKeyCode::D => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
VirtualKeyCode::Z => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
@@ -73,46 +111,3 @@ fn main() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Enumerate monitors and prompt user to choose one
|
||||
fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||
for (num, monitor) in event_loop.available_monitors().enumerate() {
|
||||
println!("Monitor #{}: {:?}", num, monitor.name());
|
||||
}
|
||||
|
||||
print!("Please write the number of the monitor to use: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let monitor = event_loop
|
||||
.available_monitors()
|
||||
.nth(num)
|
||||
.expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {:?}", monitor.name());
|
||||
|
||||
monitor
|
||||
}
|
||||
|
||||
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
|
||||
for (i, video_mode) in monitor.video_modes().enumerate() {
|
||||
println!("Video mode #{}: {}", i, video_mode);
|
||||
}
|
||||
|
||||
print!("Please write the number of the video mode to use: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let mut num = String::new();
|
||||
stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
let video_mode = monitor
|
||||
.video_modes()
|
||||
.nth(num)
|
||||
.expect("Please enter a valid ID");
|
||||
|
||||
println!("Using {}", video_mode);
|
||||
|
||||
video_mode
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -21,7 +23,7 @@ fn main() {
|
||||
ElementState::Released,
|
||||
VirtualKeyCode::{N, Y},
|
||||
};
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => {
|
||||
@@ -63,7 +65,7 @@ fn main() {
|
||||
// event loop (i.e. if it's a multi-window application), you need to
|
||||
// drop the window. That closes it, and results in `Destroyed` being
|
||||
// sent.
|
||||
*control_flow = ControlFlow::Exit;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
N => {
|
||||
|
||||
99
examples/ime.rs
Normal file
99
examples/ime.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use log::LevelFilter;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::PhysicalPosition,
|
||||
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new()
|
||||
.with_level(LevelFilter::Trace)
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
println!("IME position will system default");
|
||||
println!("Click to set IME position to cursor's");
|
||||
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut ime_allowed = true;
|
||||
window.set_ime_allowed(ime_allowed);
|
||||
|
||||
let mut may_show_ime = false;
|
||||
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
|
||||
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CursorMoved { position, .. },
|
||||
..
|
||||
} => {
|
||||
cursor_position = position;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!(
|
||||
"Setting ime position to {}, {}",
|
||||
cursor_position.x, cursor_position.y
|
||||
);
|
||||
ime_pos = cursor_position;
|
||||
if may_show_ime {
|
||||
window.set_ime_position(ime_pos);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Ime(event),
|
||||
..
|
||||
} => {
|
||||
println!("{:?}", event);
|
||||
may_show_ime = event != Ime::Disabled;
|
||||
if may_show_ime {
|
||||
window.set_ime_position(ime_pos);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::ReceivedCharacter(ch),
|
||||
..
|
||||
} => {
|
||||
println!("ch: {:?}", ch);
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
..
|
||||
} => {
|
||||
println!("key: {:?}", input);
|
||||
|
||||
if input.state == ElementState::Pressed
|
||||
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
|
||||
{
|
||||
ime_allowed = !ime_allowed;
|
||||
window.set_ime_allowed(ime_allowed);
|
||||
println!("\nIME: {}\n", ime_allowed);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
|
||||
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
extern crate winit;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::event::{Event, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::WindowBuilder;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
|
||||
// Keyboard input event to handle minimize via a hotkey
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
window_id,
|
||||
} => {
|
||||
if window_id == window.id() {
|
||||
// Pressing the 'M' key will minimize the window
|
||||
if input.virtual_keycode == Some(VirtualKeyCode::M) {
|
||||
window.set_minimized(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{DeviceEvent, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -14,29 +16,41 @@ fn main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
r"
|
||||
When using so called 'natural scrolling' (scrolling that acts like on a touch screen), this is what to expect:
|
||||
|
||||
Moving your finger downwards on a scroll wheel should make the window move down, and you should see a positive Y scroll value.
|
||||
|
||||
When moving fingers on a trackpad down and to the right, you should see positive X and Y deltas, and the window should move down and to the right.
|
||||
|
||||
With reverse scrolling, you should see the inverse behavior.
|
||||
|
||||
In both cases the example window should move like the content of a scroll area in any other application.
|
||||
|
||||
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
|
||||
);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
DeviceEvent::MouseWheel { delta } => match delta {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
||||
println!("mouse wheel Line Delta: ({},{})", x, y);
|
||||
let pixels_per_line = 120.0;
|
||||
let mut pos = window.outer_position().unwrap();
|
||||
pos.x -= (x * pixels_per_line) as i32;
|
||||
pos.y -= (y * pixels_per_line) as i32;
|
||||
pos.x += (x * pixels_per_line) as i32;
|
||||
pos.y += (y * pixels_per_line) as i32;
|
||||
window.set_outer_position(pos)
|
||||
}
|
||||
winit::event::MouseScrollDelta::PixelDelta(p) => {
|
||||
println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y);
|
||||
let mut pos = window.outer_position().unwrap();
|
||||
pos.x -= p.x as i32;
|
||||
pos.y -= p.y as i32;
|
||||
pos.x += p.x as i32;
|
||||
pos.y += p.y as i32;
|
||||
window.set_outer_position(pos)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
@@ -6,8 +8,8 @@ fn main() {
|
||||
use winit::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{CursorIcon, Fullscreen, WindowBuilder},
|
||||
event_loop::EventLoop,
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
@@ -34,10 +36,10 @@ fn main() {
|
||||
// We need to update our chosen video mode if the window
|
||||
// was moved to an another monitor, so that the window
|
||||
// appears on this monitor instead when we go fullscreen
|
||||
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
|
||||
let previous_video_mode = video_modes.get(video_mode_id).cloned();
|
||||
video_modes = window.current_monitor().unwrap().video_modes().collect();
|
||||
video_mode_id = video_mode_id.min(video_modes.len());
|
||||
let video_mode = video_modes.iter().nth(video_mode_id);
|
||||
let video_mode = video_modes.get(video_mode_id);
|
||||
|
||||
// Different monitors may support different video modes,
|
||||
// and the index we chose previously may now point to a
|
||||
@@ -45,7 +47,7 @@ fn main() {
|
||||
if video_mode != previous_video_mode.as_ref() {
|
||||
println!(
|
||||
"Window moved to another monitor, picked video mode: {}",
|
||||
video_modes.iter().nth(video_mode_id).unwrap()
|
||||
video_modes.get(video_mode_id).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -77,19 +79,30 @@ fn main() {
|
||||
Right => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!(
|
||||
"Picking video mode: {}",
|
||||
video_modes.iter().nth(video_mode_id).unwrap()
|
||||
);
|
||||
println!("Picking video mode: {}", video_modes[video_mode_id]);
|
||||
}
|
||||
F => window.set_fullscreen(match (state, modifiers.alt()) {
|
||||
(true, false) => Some(Fullscreen::Borderless(None)),
|
||||
(true, true) => Some(Fullscreen::Exclusive(
|
||||
video_modes.iter().nth(video_mode_id).unwrap().clone(),
|
||||
)),
|
||||
(true, true) => {
|
||||
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
|
||||
}
|
||||
(false, _) => None,
|
||||
}),
|
||||
G => window.set_cursor_grab(state).unwrap(),
|
||||
L if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
G if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
G | L if !state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
|
||||
println!("error: {}", err);
|
||||
}
|
||||
}
|
||||
H => window.set_cursor_visible(!state),
|
||||
I => {
|
||||
println!("Info:");
|
||||
@@ -146,9 +159,9 @@ fn main() {
|
||||
});
|
||||
}
|
||||
event_loop.run(move |event, _event_loop, control_flow| {
|
||||
*control_flow = match !window_senders.is_empty() {
|
||||
true => ControlFlow::Wait,
|
||||
false => ControlFlow::Exit,
|
||||
match !window_senders.is_empty() {
|
||||
true => control_flow.set_wait(),
|
||||
false => control_flow.set_exit(),
|
||||
};
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
@@ -173,7 +186,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
@@ -14,11 +16,14 @@ fn main() {
|
||||
let mut windows = HashMap::new();
|
||||
for _ in 0..3 {
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
println!("Opened a new window: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
|
||||
println!("Press N to open a new window.");
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
@@ -30,18 +35,21 @@ fn main() {
|
||||
windows.remove(&window_id);
|
||||
|
||||
if windows.is_empty() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode: Some(VirtualKeyCode::N),
|
||||
..
|
||||
},
|
||||
is_synthetic: false,
|
||||
..
|
||||
} => {
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
let window = Window::new(event_loop).unwrap();
|
||||
println!("Opened a new window: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -17,11 +19,11 @@ fn main() {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use std::{thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
use std::{thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
@@ -25,13 +27,13 @@ fn main() {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -14,17 +16,19 @@ fn main() {
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hit space to toggle resizability.")
|
||||
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
.with_inner_size(LogicalSize::new(600.0, 300.0))
|
||||
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
.with_max_inner_size(LogicalSize::new(800.0, 400.0))
|
||||
.with_resizable(resizable)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::PhysicalPosition,
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
println!("Ime position will system default");
|
||||
println!("Click to set ime position to cursor's");
|
||||
|
||||
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CursorMoved { position, .. },
|
||||
..
|
||||
} => {
|
||||
cursor_position = position;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!(
|
||||
"Setting ime position to {}, {}",
|
||||
cursor_position.x, cursor_position.y
|
||||
);
|
||||
window.set_ime_position(cursor_position);
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use instant::Instant;
|
||||
use std::time::Duration;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -24,16 +26,16 @@ fn main() {
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length)
|
||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
|
||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
||||
println!("\nTimer\n");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
} => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -18,14 +20,14 @@ fn main() {
|
||||
window.set_title("A fantastic window!");
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
} => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::event_loop::EventLoop;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -13,30 +15,19 @@ pub fn main() {
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
let canvas = window.canvas();
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
|
||||
body.append_child(&canvas)
|
||||
.expect("Append canvas to HTML body");
|
||||
}
|
||||
let log_list = wasm::create_log_list(&window);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
log::debug!("{:?}", event);
|
||||
wasm::log_event(&log_list, &event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
@@ -48,11 +39,52 @@ pub fn main() {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use winit::{event::Event, window::Window};
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run() {
|
||||
console_log::init_with_level(log::Level::Debug);
|
||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||
|
||||
#[allow(clippy::main_recursion)]
|
||||
super::main();
|
||||
}
|
||||
|
||||
pub fn create_log_list(window: &Window) -> web_sys::Element {
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
let canvas = window.canvas();
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
|
||||
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
|
||||
canvas.style().set_css_text("background-color: crimson;");
|
||||
body.append_child(&canvas).unwrap();
|
||||
|
||||
let log_header = document.create_element("h2").unwrap();
|
||||
log_header.set_text_content(Some("Event Log"));
|
||||
body.append_child(&log_header).unwrap();
|
||||
|
||||
let log_list = document.create_element("ul").unwrap();
|
||||
body.append_child(&log_list).unwrap();
|
||||
log_list
|
||||
}
|
||||
|
||||
pub fn log_event(log_list: &web_sys::Element, event: &Event<()>) {
|
||||
log::debug!("{:?}", event);
|
||||
|
||||
// Getting access to browser logs requires a lot of setup on mobile devices.
|
||||
// So we implement this basic logging system into the page to give developers an easy alternative.
|
||||
// As a bonus its also kind of handy on desktop.
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let log = document.create_element("li").unwrap();
|
||||
log.set_text_content(Some(&format!("{:?}", event)));
|
||||
log_list
|
||||
.insert_before(&log, log_list.first_child().as_ref())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
@@ -16,14 +18,14 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// This example is used by developers to test various window functions.
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::{LogicalSize, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::{DeviceEventFilter, EventLoop},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -30,8 +32,10 @@ fn main() {
|
||||
let mut minimized = false;
|
||||
let mut visible = true;
|
||||
|
||||
event_loop.set_device_event_filter(DeviceEventFilter::Never);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::DeviceEvent {
|
||||
@@ -58,67 +62,69 @@ fn main() {
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match input {
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
} => match key {
|
||||
VirtualKeyCode::E => {
|
||||
fn area(size: PhysicalSize<u32>) -> u32 {
|
||||
size.width * size.height
|
||||
}
|
||||
} => match key {
|
||||
VirtualKeyCode::E => {
|
||||
fn area(size: PhysicalSize<u32>) -> u32 {
|
||||
size.width * size.height
|
||||
}
|
||||
|
||||
let monitor = window.current_monitor().unwrap();
|
||||
if let Some(mode) = monitor
|
||||
.video_modes()
|
||||
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
|
||||
{
|
||||
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
|
||||
} else {
|
||||
eprintln!("no video modes available");
|
||||
}
|
||||
let monitor = window.current_monitor().unwrap();
|
||||
if let Some(mode) = monitor
|
||||
.video_modes()
|
||||
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
|
||||
{
|
||||
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
|
||||
} else {
|
||||
eprintln!("no video modes available");
|
||||
}
|
||||
VirtualKeyCode::F => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
let monitor = window.current_monitor();
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
|
||||
}
|
||||
}
|
||||
VirtualKeyCode::F => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
let monitor = window.current_monitor();
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
|
||||
}
|
||||
VirtualKeyCode::P => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||
}
|
||||
}
|
||||
VirtualKeyCode::P => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||
}
|
||||
VirtualKeyCode::M => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
VirtualKeyCode::Q => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
VirtualKeyCode::V => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
VirtualKeyCode::M => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
VirtualKeyCode::Q => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
VirtualKeyCode::V => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
extern crate image;
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
window::{Icon, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -30,12 +31,12 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
use winit::event::WindowEvent::*;
|
||||
match event {
|
||||
CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
CloseRequested => control_flow.set_exit(),
|
||||
DroppedFile(path) => {
|
||||
window.set_window_icon(Some(load_icon(&path)));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
// Limit this example to only compatible platforms.
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
@@ -6,7 +8,8 @@
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
target_os = "openbsd",
|
||||
target_os = "android",
|
||||
))]
|
||||
fn main() {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
@@ -14,7 +17,7 @@ fn main() {
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
event_loop::EventLoop,
|
||||
platform::run_return::EventLoopExtRunReturn,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
@@ -30,7 +33,7 @@ fn main() {
|
||||
|
||||
while !quit {
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
control_flow.set_wait();
|
||||
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
@@ -45,7 +48,7 @@ fn main() {
|
||||
quit = true;
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
control_flow.set_exit();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@@ -57,7 +60,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
|
||||
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support run_return.");
|
||||
}
|
||||
|
||||
9
run-wasm/Cargo.toml
Normal file
9
run-wasm/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "run-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cargo-run-wasm = "0.1.0"
|
||||
3
run-wasm/src/main.rs
Normal file
3
run-wasm/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
cargo_run_wasm::run_wasm();
|
||||
}
|
||||
26
src/dpi.rs
26
src/dpi.rs
@@ -35,8 +35,9 @@
|
||||
//!
|
||||
//! ### Position and Size types
|
||||
//!
|
||||
//! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the
|
||||
//! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor.
|
||||
//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the
|
||||
//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels
|
||||
//! divided by the scale factor.
|
||||
//! All of Winit's functions return physical types, but can take either logical or physical
|
||||
//! coordinates as input, allowing you to use the most convenient coordinate system for your
|
||||
//! particular application.
|
||||
@@ -46,19 +47,18 @@
|
||||
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
|
||||
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
|
||||
//! will truncate the fractional part of the float, rather than properly round to the nearest
|
||||
//! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the
|
||||
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
|
||||
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
|
||||
//! int, although rounding lessens the problem.
|
||||
//!
|
||||
//! ### Events
|
||||
//!
|
||||
//! Winit will dispatch a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged)
|
||||
//! event whenever a window's scale factor has changed. This can happen if the user drags their
|
||||
//! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their
|
||||
//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how
|
||||
//! the platform changes the window's size to reflect the new scale factor. If a window hasn't
|
||||
//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event,
|
||||
//! then its scale factor can be found by calling [window.scale_factor()].
|
||||
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
|
||||
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
|
||||
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
|
||||
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
|
||||
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
|
||||
//! can be found by calling [`window.scale_factor()`].
|
||||
//!
|
||||
//! ## How is the scale factor calculated?
|
||||
//!
|
||||
@@ -77,7 +77,7 @@
|
||||
//! currently uses a three-pronged approach:
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
||||
//! + If not present, use the value set in `Xft.dpi` in Xresources.
|
||||
//! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
||||
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
||||
//!
|
||||
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
|
||||
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
|
||||
@@ -93,9 +93,11 @@
|
||||
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
|
||||
//! both the screen scaling and the browser zoom level and can go below `1.0`.
|
||||
//!
|
||||
//!
|
||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
||||
//! [window.scale_factor()]: crate::window::Window::scale_factor
|
||||
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
|
||||
//! [`window.scale_factor()`]: crate::window::Window::scale_factor
|
||||
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
||||
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
||||
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
||||
|
||||
265
src/event.rs
265
src/event.rs
@@ -1,10 +1,10 @@
|
||||
//! The `Event` enum and assorted supporting types.
|
||||
//! The [`Event`] enum and assorted supporting types.
|
||||
//!
|
||||
//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get
|
||||
//! These are sent to the closure given to [`EventLoop::run(...)`], where they get
|
||||
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
||||
//!
|
||||
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
|
||||
//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this:
|
||||
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let mut control_flow = ControlFlow::Poll;
|
||||
@@ -29,13 +29,16 @@
|
||||
//! event_handler(LoopDestroyed, ..., &mut control_flow);
|
||||
//! ```
|
||||
//!
|
||||
//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully
|
||||
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
|
||||
//! describes what happens in what order.
|
||||
//!
|
||||
//! [event_loop_run]: crate::event_loop::EventLoop::run
|
||||
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
|
||||
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
use instant::Instant;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(doc)]
|
||||
use crate::window::Window;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
platform_impl,
|
||||
@@ -71,9 +74,107 @@ pub enum Event<'a, T: 'static> {
|
||||
UserEvent(T),
|
||||
|
||||
/// Emitted when the application has been suspended.
|
||||
///
|
||||
/// # Portability
|
||||
///
|
||||
/// Not all platforms support the notion of suspending applications, and there may be no
|
||||
/// technical way to guarantee being able to emit a `Suspended` event if the OS has
|
||||
/// no formal application lifecycle (currently only Android and iOS do). For this reason,
|
||||
/// Winit does not currently try to emit pseudo `Suspended` events before the application
|
||||
/// quits on platforms without an application lifecycle.
|
||||
///
|
||||
/// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally
|
||||
/// driven by multiple platform-specific events, and that there may be subtle differences across
|
||||
/// platforms with how these internal events are delivered, it's recommended that applications
|
||||
/// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events.
|
||||
///
|
||||
/// Also see [`Resumed`] notes.
|
||||
///
|
||||
/// ## Android
|
||||
///
|
||||
/// On Android, the `Suspended` event is only sent when the application's associated
|
||||
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
|
||||
/// lifecycle event but there may technically be a discrepancy.
|
||||
///
|
||||
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
|
||||
///
|
||||
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
|
||||
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
|
||||
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
|
||||
///
|
||||
/// After being `Suspended` on Android applications must drop all render surfaces before
|
||||
/// the event callback completes, which may be re-created when the application is next [`Resumed`].
|
||||
///
|
||||
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
|
||||
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
|
||||
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
|
||||
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
|
||||
///
|
||||
/// ## iOS
|
||||
///
|
||||
/// On iOS, the `Suspended` event is currently emitted in response to an
|
||||
/// [`applicationWillResignActive`] callback which means that the application is
|
||||
/// about to transition from the active to inactive state (according to the
|
||||
/// [iOS application lifecycle]).
|
||||
///
|
||||
/// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive
|
||||
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
|
||||
///
|
||||
/// [`Resumed`]: Self::Resumed
|
||||
Suspended,
|
||||
|
||||
/// Emitted when the application has been resumed.
|
||||
///
|
||||
/// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a
|
||||
/// formal suspend/resume lifecycle. For systems without a standard suspend/resume lifecycle
|
||||
/// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init]
|
||||
/// event.
|
||||
///
|
||||
/// # Portability
|
||||
///
|
||||
/// It's recommended that applications should only initialize their graphics context and create
|
||||
/// a window after they have received their first `Resumed` event. Some systems
|
||||
/// (specifically Android) won't allow applications to create a render surface until they are
|
||||
/// resumed.
|
||||
///
|
||||
/// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally
|
||||
/// driven by multiple platform-specific events, and that there may be subtle differences across
|
||||
/// platforms with how these internal events are delivered, it's recommended that applications
|
||||
/// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events.
|
||||
///
|
||||
/// Also see [`Suspended`] notes.
|
||||
///
|
||||
/// ## Android
|
||||
///
|
||||
/// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is
|
||||
/// expected to closely correlate with the [`onResume`] lifecycle event but there may technically
|
||||
/// be a discrepancy.
|
||||
///
|
||||
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
|
||||
///
|
||||
/// Applications that need to run on Android must wait until they have been `Resumed`
|
||||
/// before they will be able to create a render surface (such as an `EGLSurface`,
|
||||
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a
|
||||
/// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their
|
||||
/// render surfaces are invalid and should be dropped.
|
||||
///
|
||||
/// Also see [`Suspended`] notes.
|
||||
///
|
||||
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
|
||||
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
|
||||
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
|
||||
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
|
||||
///
|
||||
/// ## iOS
|
||||
///
|
||||
/// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`]
|
||||
/// callback which means the application is "active" (according to the
|
||||
/// [iOS application lifecycle]).
|
||||
///
|
||||
/// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive
|
||||
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
|
||||
///
|
||||
/// [`Suspended`]: Self::Suspended
|
||||
Resumed,
|
||||
|
||||
/// Emitted when all of the event loop's input events have been processed and redraw processing
|
||||
@@ -88,27 +189,30 @@ pub enum Event<'a, T: 'static> {
|
||||
/// can render here unconditionally for simplicity.
|
||||
MainEventsCleared,
|
||||
|
||||
/// Emitted after `MainEventsCleared` when a window should be redrawn.
|
||||
/// Emitted after [`MainEventsCleared`] when a window should be redrawn.
|
||||
///
|
||||
/// This gets triggered in two scenarios:
|
||||
/// - The OS has performed an operation that's invalidated the window's contents (such as
|
||||
/// resizing the window).
|
||||
/// - The application has explicitly requested a redraw via
|
||||
/// [`Window::request_redraw`](crate::window::Window::request_redraw).
|
||||
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
|
||||
///
|
||||
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
|
||||
/// into a single event, to help avoid duplicating rendering work.
|
||||
///
|
||||
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
|
||||
/// something changes, like most non-game GUIs.
|
||||
///
|
||||
/// [`MainEventsCleared`]: Self::MainEventsCleared
|
||||
RedrawRequested(WindowId),
|
||||
|
||||
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
|
||||
/// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to
|
||||
/// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
|
||||
/// immediately after `MainEventsCleared`.
|
||||
///
|
||||
/// This event is useful for doing any cleanup or bookkeeping work after all the rendering
|
||||
/// tasks have been completed.
|
||||
///
|
||||
/// [`RedrawRequested`]: Self::RedrawRequested
|
||||
RedrawEventsCleared,
|
||||
|
||||
/// Emitted when the event loop is being shut down.
|
||||
@@ -183,9 +287,11 @@ impl<'a, T> Event<'a, T> {
|
||||
/// Describes the reason the event loop is resuming.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StartCause {
|
||||
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
|
||||
/// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the
|
||||
/// moment the timeout was requested and the requested resume time. The actual resume time is
|
||||
/// guaranteed to be equal to or after the requested resume time.
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
ResumeTimeReached {
|
||||
start: Instant,
|
||||
requested_resume: Instant,
|
||||
@@ -199,14 +305,16 @@ pub enum StartCause {
|
||||
},
|
||||
|
||||
/// Sent if the event loop is being resumed after the loop's control flow was set to
|
||||
/// `ControlFlow::Poll`.
|
||||
/// [`ControlFlow::Poll`].
|
||||
///
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
Poll,
|
||||
|
||||
/// Sent once, immediately after `run` is called. Indicates that the loop was just initialized.
|
||||
Init,
|
||||
}
|
||||
|
||||
/// Describes an event from a `Window`.
|
||||
/// Describes an event from a [`Window`].
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum WindowEvent<'a> {
|
||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||
@@ -240,6 +348,8 @@ pub enum WindowEvent<'a> {
|
||||
HoveredFileCancelled,
|
||||
|
||||
/// The window received a unicode character.
|
||||
///
|
||||
/// See also the [`Ime`](Self::Ime) event for more complex character sequences.
|
||||
ReceivedCharacter(char),
|
||||
|
||||
/// The window gained or lost focus.
|
||||
@@ -265,11 +375,21 @@ pub enum WindowEvent<'a> {
|
||||
|
||||
/// The keyboard modifiers have changed.
|
||||
///
|
||||
/// Platform-specific behavior:
|
||||
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** This API is currently unimplemented on the web. This isn't by design - it's an
|
||||
/// issue, and it should get fixed - but it's the current state of the API.
|
||||
ModifiersChanged(ModifiersState),
|
||||
|
||||
/// An event from an input method.
|
||||
///
|
||||
/// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web:** Unsupported.
|
||||
Ime(Ime),
|
||||
|
||||
/// The cursor has moved on the window.
|
||||
CursorMoved {
|
||||
device_id: DeviceId,
|
||||
@@ -350,8 +470,19 @@ pub enum WindowEvent<'a> {
|
||||
/// Applications might wish to react to this to change the theme of the content of the window
|
||||
/// when the system changes the window theme.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// At the moment this is only supported on Windows.
|
||||
ThemeChanged(Theme),
|
||||
|
||||
/// The window has been occluded (completely hidden from view).
|
||||
///
|
||||
/// This is different to window visibility as it depends on whether the window is closed,
|
||||
/// minimised, set invisible, or fully occluded by another window.
|
||||
///
|
||||
/// Platform-specific behavior:
|
||||
/// - **iOS / Android / Web / Wayland / Windows:** Unsupported.
|
||||
Occluded(bool),
|
||||
}
|
||||
|
||||
impl Clone for WindowEvent<'static> {
|
||||
@@ -376,7 +507,7 @@ impl Clone for WindowEvent<'static> {
|
||||
input: *input,
|
||||
is_synthetic: *is_synthetic,
|
||||
},
|
||||
|
||||
Ime(preedit_state) => Ime(preedit_state.clone()),
|
||||
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
|
||||
#[allow(deprecated)]
|
||||
CursorMoved {
|
||||
@@ -441,6 +572,7 @@ impl Clone for WindowEvent<'static> {
|
||||
ScaleFactorChanged { .. } => {
|
||||
unreachable!("Static event can't be about scale factor changing")
|
||||
}
|
||||
Occluded(occluded) => Occluded(*occluded),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -468,6 +600,7 @@ impl<'a> WindowEvent<'a> {
|
||||
is_synthetic,
|
||||
}),
|
||||
ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)),
|
||||
Ime(event) => Some(Ime(event)),
|
||||
#[allow(deprecated)]
|
||||
CursorMoved {
|
||||
device_id,
|
||||
@@ -525,6 +658,7 @@ impl<'a> WindowEvent<'a> {
|
||||
Touch(touch) => Some(Touch(touch)),
|
||||
ThemeChanged(theme) => Some(ThemeChanged(theme)),
|
||||
ScaleFactorChanged { .. } => None,
|
||||
Occluded(occluded) => Some(Occluded(occluded)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,7 +672,7 @@ impl<'a> WindowEvent<'a> {
|
||||
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
|
||||
|
||||
impl DeviceId {
|
||||
/// Returns a dummy `DeviceId`, useful for unit testing.
|
||||
/// Returns a dummy id, useful for unit testing.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
@@ -567,7 +701,7 @@ pub enum DeviceEvent {
|
||||
|
||||
/// Change in physical position of a pointing device.
|
||||
///
|
||||
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
|
||||
/// This represents raw, unfiltered physical motion. Not to be confused with [`WindowEvent::CursorMoved`].
|
||||
MouseMotion {
|
||||
/// (x, y) change in position in unspecified units.
|
||||
///
|
||||
@@ -580,7 +714,7 @@ pub enum DeviceEvent {
|
||||
delta: MouseScrollDelta,
|
||||
},
|
||||
|
||||
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
|
||||
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
|
||||
/// that winit supports on this platform, including mouse devices. If the device is a mouse
|
||||
/// device then this will be reported alongside the MouseMotion event.
|
||||
Motion {
|
||||
@@ -627,6 +761,72 @@ pub struct KeyboardInput {
|
||||
pub modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
|
||||
///
|
||||
/// This is also called a "composition event".
|
||||
///
|
||||
/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::ReceivedCharacter`].
|
||||
/// However, one couldn't possibly have a key for every single unicode character that the user might want to type
|
||||
/// - so the solution operating systems employ is to allow the user to type these using _a sequence of keypresses_ instead.
|
||||
///
|
||||
/// A prominent example of this is accents - many keyboard layouts allow you to first click the "accent key", and then
|
||||
/// the character you want to apply the accent to. This will generate the following event sequence:
|
||||
/// ```ignore
|
||||
/// // Press "`" key
|
||||
/// Ime::Preedit("`", Some(0), Some(0))
|
||||
/// // Press "E" key
|
||||
/// Ime::Commit("é")
|
||||
/// ```
|
||||
///
|
||||
/// Additionally, certain input devices are configured to display a candidate box that allow the user to select the
|
||||
/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_position`].)
|
||||
///
|
||||
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keybaord the following event
|
||||
/// sequence could be obtained:
|
||||
/// ```ignore
|
||||
/// // Press "A" key
|
||||
/// Ime::Preedit("a", Some(1), Some(1))
|
||||
/// // Press "B" key
|
||||
/// Ime::Preedit("a b", Some(3), Some(3))
|
||||
/// // Press left arrow key
|
||||
/// Ime::Preedit("a b", Some(1), Some(1))
|
||||
/// // Press space key
|
||||
/// Ime::Preedit("啊b", Some(3), Some(3))
|
||||
/// // Press space key
|
||||
/// Ime::Commit("啊不")
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Ime {
|
||||
/// Notifies when the IME was enabled.
|
||||
///
|
||||
/// After getting this event you could receive [`Preedit`](Self::Preedit) and
|
||||
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests
|
||||
/// like [`Window::set_ime_position`].
|
||||
Enabled,
|
||||
|
||||
/// Notifies when a new composing text should be set at the cursor position.
|
||||
///
|
||||
/// The value represents a pair of the preedit string and the cursor begin position and end
|
||||
/// position. When it's `None`, the cursor should be hidden.
|
||||
///
|
||||
/// The cursor position is byte-wise indexed.
|
||||
Preedit(String, Option<(usize, usize)>),
|
||||
|
||||
/// Notifies when text should be inserted into the editor widget.
|
||||
///
|
||||
/// Any pending [`Preedit`](Self::Preedit) must be cleared.
|
||||
Commit(String),
|
||||
|
||||
/// Notifies when the IME was disabled.
|
||||
///
|
||||
/// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or
|
||||
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You can
|
||||
/// also stop issuing IME related requests like [`Window::set_ime_position`] and clear pending
|
||||
/// preedit text.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Describes touch-screen input state.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
@@ -639,18 +839,18 @@ pub enum TouchPhase {
|
||||
|
||||
/// Represents a touch event
|
||||
///
|
||||
/// Every time the user touches the screen, a new `Start` event with an unique
|
||||
/// identifier for the finger is generated. When the finger is lifted, an `End`
|
||||
/// Every time the user touches the screen, a new [`TouchPhase::Started`] event with an unique
|
||||
/// identifier for the finger is generated. When the finger is lifted, an [`TouchPhase::Ended`]
|
||||
/// event is generated with the same finger id.
|
||||
///
|
||||
/// After a `Start` event has been emitted, there may be zero or more `Move`
|
||||
/// After a `Started` event has been emitted, there may be zero or more `Move`
|
||||
/// events when the finger is moved or the touch pressure changes.
|
||||
///
|
||||
/// The finger id may be reused by the system after an `End` event. The user
|
||||
/// should assume that a new `Start` event received with the same id has nothing
|
||||
/// The finger id may be reused by the system after an `Ended` event. The user
|
||||
/// should assume that a new `Started` event received with the same id has nothing
|
||||
/// to do with the old finger and is a new finger.
|
||||
///
|
||||
/// A `Cancelled` event is emitted when the system has canceled tracking this
|
||||
/// A [`TouchPhase::Cancelled`] event is emitted when the system has canceled tracking this
|
||||
/// touch, such as when the window loses focus, or on iOS if the user moves the
|
||||
/// device against their face.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
@@ -704,8 +904,9 @@ pub enum Force {
|
||||
|
||||
impl Force {
|
||||
/// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
|
||||
///
|
||||
/// Instead of normalizing the force, you should prefer to handle
|
||||
/// `Force::Calibrated` so that the amount of force the user has to apply is
|
||||
/// [`Force::Calibrated`] so that the amount of force the user has to apply is
|
||||
/// consistent across devices.
|
||||
pub fn normalized(&self) -> f64 {
|
||||
match self {
|
||||
@@ -759,15 +960,23 @@ pub enum MouseScrollDelta {
|
||||
/// Amount in lines or rows to scroll in the horizontal
|
||||
/// and vertical directions.
|
||||
///
|
||||
/// Positive values indicate movement forward
|
||||
/// (away from the user) or rightwards.
|
||||
/// Positive values indicate that the content that is being scrolled should move
|
||||
/// right and down (revealing more content left and up).
|
||||
LineDelta(f32, f32),
|
||||
|
||||
/// Amount in pixels to scroll in the horizontal and
|
||||
/// vertical direction.
|
||||
///
|
||||
/// Scroll events are expressed as a PixelDelta if
|
||||
/// Scroll events are expressed as a `PixelDelta` if
|
||||
/// supported by the device (eg. a touchpad) and
|
||||
/// platform.
|
||||
///
|
||||
/// Positive values indicate that the content being scrolled should
|
||||
/// move right/down.
|
||||
///
|
||||
/// For a 'natural scrolling' touch pad (that acts like a touch screen)
|
||||
/// this means moving your fingers right and down should give positive values,
|
||||
/// and move the content right and down (to reveal more things left and up).
|
||||
PixelDelta(PhysicalPosition<f64>),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,117 @@
|
||||
//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`.
|
||||
//! The [`EventLoop`] struct and assorted supporting types, including
|
||||
//! [`ControlFlow`].
|
||||
//!
|
||||
//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy]
|
||||
//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method.
|
||||
//! If you want to send custom events to the event loop, use
|
||||
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
|
||||
//! [`send_event`](`EventLoopProxy::send_event`) method.
|
||||
//!
|
||||
//! See the root-level documentation for information on how to create and use an event loop to
|
||||
//! handle events.
|
||||
//!
|
||||
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
|
||||
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
|
||||
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
|
||||
use instant::Instant;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::{error, fmt};
|
||||
|
||||
use instant::Instant;
|
||||
use once_cell::sync::OnceCell;
|
||||
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
|
||||
|
||||
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
/// the events loop.
|
||||
///
|
||||
/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()`
|
||||
/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`]
|
||||
/// initializes everything that will be required to create windows. For example on Linux creating
|
||||
/// an event loop opens a connection to the X or Wayland server.
|
||||
///
|
||||
/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs.
|
||||
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
|
||||
///
|
||||
/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic
|
||||
/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
|
||||
/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the
|
||||
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
|
||||
/// Note that this cannot be shared across threads (due to platform-dependant logic
|
||||
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the
|
||||
/// [`Window`] created from this _can_ be sent to an other thread, and the
|
||||
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
pub struct EventLoop<T: 'static> {
|
||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
/// Target that associates windows with an `EventLoop`.
|
||||
/// Target that associates windows with an [`EventLoop`].
|
||||
///
|
||||
/// This type exists to allow you to create new windows while Winit executes
|
||||
/// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
|
||||
/// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for
|
||||
/// EventLoop<T>`), so functions that take this as a parameter can also take
|
||||
/// `&EventLoop`.
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
/// Object that allows building the event loop.
|
||||
///
|
||||
/// This is used to make specifying options that affect the whole application
|
||||
/// easier. But note that constructing multiple event loops is not supported.
|
||||
#[derive(Default)]
|
||||
pub struct EventLoopBuilder<T: 'static> {
|
||||
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
|
||||
_p: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl EventLoopBuilder<()> {
|
||||
/// Start building a new event loop.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::with_user_event()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopBuilder<T> {
|
||||
/// Start building a new event loop, with the given type as the user event
|
||||
/// type.
|
||||
#[inline]
|
||||
pub fn with_user_event() -> Self {
|
||||
Self {
|
||||
platform_specific: Default::default(),
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new event loop.
|
||||
///
|
||||
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
|
||||
/// and only once per application.***
|
||||
///
|
||||
/// Attempting to create the event loop on a different thread, or multiple event loops in
|
||||
/// the same application, will panic. This restriction isn't
|
||||
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
|
||||
/// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
|
||||
/// in the relevant [`platform`] module if the target platform supports creating an event loop on
|
||||
/// any thread.
|
||||
///
|
||||
/// Calling this function will result in display backend initialisation.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** Backend type can be controlled using an environment variable
|
||||
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
||||
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
|
||||
/// will fall back on X11. If this variable is set with any other value, winit will panic.
|
||||
///
|
||||
/// [`platform`]: crate::platform
|
||||
#[inline]
|
||||
pub fn build(&mut self) -> EventLoop<T> {
|
||||
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
|
||||
if EVENT_LOOP_CREATED.set(()).is_err() {
|
||||
panic!("Creating EventLoop multiple times is not supported.");
|
||||
}
|
||||
// Certain platforms accept a mutable reference in their API.
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoop<T> {
|
||||
@@ -57,24 +126,28 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set by the user callback given to the `EventLoop::run` method.
|
||||
/// Set by the user callback given to the [`EventLoop::run`] method.
|
||||
///
|
||||
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared]
|
||||
/// is emitted. Defaults to `Poll`.
|
||||
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
|
||||
///
|
||||
/// Defaults to [`Poll`].
|
||||
///
|
||||
/// ## Persistency
|
||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
||||
/// given run loop. The only exception to this is `Exit` which, once set, cannot be unset. Changes
|
||||
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
|
||||
/// the control flow to `Poll`.
|
||||
///
|
||||
/// [events_cleared]: crate::event::Event::RedrawEventsCleared
|
||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
||||
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
|
||||
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
|
||||
/// reset the control flow to [`Poll`].
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
/// [`Poll`]: Self::Poll
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ControlFlow {
|
||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||
/// whether or not new events are available to process.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
|
||||
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
|
||||
/// example when the scaling of the page has changed. This should be treated as an implementation
|
||||
@@ -84,56 +157,102 @@ pub enum ControlFlow {
|
||||
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),
|
||||
/// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// `control_flow` cannot be changed from `Exit`, and any future attempts to do so will result
|
||||
/// in the `control_flow` parameter being reset to `Exit`.
|
||||
Exit,
|
||||
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
|
||||
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
|
||||
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
|
||||
///
|
||||
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
|
||||
/// with exit code 0.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / WASM:** The supplied exit code is unused.
|
||||
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
|
||||
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
|
||||
/// [`std::process::exit`].
|
||||
///
|
||||
/// [`LoopDestroyed`]: Event::LoopDestroyed
|
||||
/// [`Exit`]: ControlFlow::Exit
|
||||
ExitWithCode(i32),
|
||||
}
|
||||
|
||||
impl ControlFlow {
|
||||
/// Alias for [`ExitWithCode`]`(0)`.
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const Exit: Self = Self::ExitWithCode(0);
|
||||
|
||||
/// Sets this to [`Poll`].
|
||||
///
|
||||
/// [`Poll`]: Self::Poll
|
||||
pub fn set_poll(&mut self) {
|
||||
*self = Self::Poll;
|
||||
}
|
||||
|
||||
/// Sets this to [`Wait`].
|
||||
///
|
||||
/// [`Wait`]: Self::Wait
|
||||
pub fn set_wait(&mut self) {
|
||||
*self = Self::Wait;
|
||||
}
|
||||
|
||||
/// Sets this to [`WaitUntil`]`(instant)`.
|
||||
///
|
||||
/// [`WaitUntil`]: Self::WaitUntil
|
||||
pub fn set_wait_until(&mut self, instant: Instant) {
|
||||
*self = Self::WaitUntil(instant);
|
||||
}
|
||||
|
||||
/// Sets this to [`ExitWithCode`]`(code)`.
|
||||
///
|
||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
||||
pub fn set_exit_with_code(&mut self, code: i32) {
|
||||
*self = Self::ExitWithCode(code);
|
||||
}
|
||||
|
||||
/// Sets this to [`Exit`].
|
||||
///
|
||||
/// [`Exit`]: Self::Exit
|
||||
pub fn set_exit(&mut self) {
|
||||
*self = Self::Exit;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ControlFlow {
|
||||
#[inline(always)]
|
||||
fn default() -> ControlFlow {
|
||||
ControlFlow::Poll
|
||||
fn default() -> Self {
|
||||
Self::Poll
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop<()> {
|
||||
/// Builds a new event loop with a `()` as the user event type.
|
||||
/// Alias for [`EventLoopBuilder::new().build()`].
|
||||
///
|
||||
/// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
|
||||
/// Attempting to create the event loop on a different thread will panic. This restriction isn't
|
||||
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
|
||||
/// porting to platforms that require it. `EventLoopExt::new_any_thread` functions are exposed
|
||||
/// in the relevant `platform` module if the target platform supports creating an event loop on
|
||||
/// any thread.
|
||||
///
|
||||
/// Usage will result in display backend initialisation, this can be controlled on linux
|
||||
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
||||
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
|
||||
/// fallback on x11. If this variable is set with any other value, winit will panic.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
|
||||
#[inline]
|
||||
pub fn new() -> EventLoop<()> {
|
||||
EventLoop::<()>::with_user_event()
|
||||
EventLoopBuilder::new().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventLoop<()> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
/// Builds a new event loop.
|
||||
///
|
||||
/// All caveats documented in [`EventLoop::new`] apply to this function.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
|
||||
pub fn with_user_event() -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
EventLoopBuilder::<T>::with_user_event().build()
|
||||
}
|
||||
|
||||
/// Hijacks the calling thread and initializes the winit event loop with the provided
|
||||
@@ -145,6 +264,11 @@ impl<T> EventLoop<T> {
|
||||
///
|
||||
/// Any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
|
||||
/// disconnects.
|
||||
///
|
||||
/// [`ControlFlow`]: crate::event_loop::ControlFlow
|
||||
#[inline]
|
||||
pub fn run<F>(self, event_handler: F) -> !
|
||||
@@ -154,7 +278,7 @@ impl<T> EventLoop<T> {
|
||||
self.event_loop.run(event_handler)
|
||||
}
|
||||
|
||||
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
|
||||
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop.
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
event_loop_proxy: self.event_loop.create_proxy(),
|
||||
@@ -190,9 +314,38 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
self.p.primary_monitor()
|
||||
}
|
||||
|
||||
/// Change [`DeviceEvent`] filter mode.
|
||||
///
|
||||
/// 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 filter at runtime to explicitly capture them again.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Wayland / Windows / macOS / iOS / Android / Web:** Unsupported.
|
||||
///
|
||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
||||
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
self.p.set_device_event_filter(_filter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to send custom events to `EventLoop`.
|
||||
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
|
||||
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
self.p.raw_display_handle()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to send custom events to [`EventLoop`].
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
event_loop_proxy: platform_impl::EventLoopProxy<T>,
|
||||
}
|
||||
@@ -206,11 +359,13 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
/// Send an event to the `EventLoop` from which this proxy was created. This emits a
|
||||
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
|
||||
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
|
||||
/// function.
|
||||
///
|
||||
/// Returns an `Err` if the associated `EventLoop` no longer exists.
|
||||
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
|
||||
///
|
||||
/// [`UserEvent(event)`]: Event::UserEvent
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
self.event_loop_proxy.send_event(event)
|
||||
}
|
||||
@@ -222,8 +377,10 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
|
||||
/// no longer exists. Contains the original event given to `send_event`.
|
||||
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
|
||||
/// no longer exists.
|
||||
///
|
||||
/// Contains the original event given to [`EventLoopProxy::send_event`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventLoopClosed<T>(pub T);
|
||||
|
||||
@@ -234,3 +391,20 @@ impl<T> fmt::Display for EventLoopClosed<T> {
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
|
||||
|
||||
/// Filter controlling the propagation of device events.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum DeviceEventFilter {
|
||||
/// Always filter out device events.
|
||||
Always,
|
||||
/// Filter out device events while the window is not focused.
|
||||
Unfocused,
|
||||
/// Report all device events regardless of window focus.
|
||||
Never,
|
||||
}
|
||||
|
||||
impl Default for DeviceEventFilter {
|
||||
fn default() -> Self {
|
||||
Self::Unfocused
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub(crate) struct Pixel {
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
#[derive(Debug)]
|
||||
/// An error produced when using `Icon::from_rgba` with invalid arguments.
|
||||
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
|
||||
pub enum BadIcon {
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
@@ -73,10 +73,6 @@ mod constructors {
|
||||
use super::*;
|
||||
|
||||
impl RgbaIcon {
|
||||
/// Creates an `Icon` from 32bpp RGBA data.
|
||||
///
|
||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 {
|
||||
@@ -123,7 +119,7 @@ impl fmt::Debug for Icon {
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Creates an `Icon` from 32bpp RGBA data.
|
||||
/// Creates an icon from 32bpp RGBA data.
|
||||
///
|
||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
||||
|
||||
26
src/lib.rs
26
src/lib.rs
@@ -31,8 +31,8 @@
|
||||
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
|
||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
||||
//! will run until the `control_flow` argument given to the closure is set to
|
||||
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the
|
||||
//! entire program terminates.
|
||||
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
|
||||
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
|
||||
//!
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
|
||||
@@ -44,7 +44,7 @@
|
||||
//! ```no_run
|
||||
//! use winit::{
|
||||
//! event::{Event, WindowEvent},
|
||||
//! event_loop::{ControlFlow, EventLoop},
|
||||
//! event_loop::EventLoop,
|
||||
//! window::WindowBuilder,
|
||||
//! };
|
||||
//!
|
||||
@@ -54,12 +54,12 @@
|
||||
//! event_loop.run(move |event, _, control_flow| {
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! *control_flow = ControlFlow::Poll;
|
||||
//! control_flow.set_poll();
|
||||
//!
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! *control_flow = ControlFlow::Wait;
|
||||
//! control_flow.set_wait();
|
||||
//!
|
||||
//! match event {
|
||||
//! Event::WindowEvent {
|
||||
@@ -67,7 +67,7 @@
|
||||
//! ..
|
||||
//! } => {
|
||||
//! println!("The close button was pressed; stopping");
|
||||
//! *control_flow = ControlFlow::Exit
|
||||
//! control_flow.set_exit();
|
||||
//! },
|
||||
//! Event::MainEventsCleared => {
|
||||
//! // Application update code.
|
||||
@@ -98,9 +98,9 @@
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
|
||||
//! retrieve the raw handle of the window (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] method), which in turn allows you to create an
|
||||
//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
|
||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
//!
|
||||
//! Note that many platforms will display garbage data in the window's client area if the
|
||||
//! application doesn't render anything to the window by the time the desktop compositor is ready to
|
||||
@@ -114,6 +114,7 @@
|
||||
//! [event_loop_run]: event_loop::EventLoop::run
|
||||
//! [`ControlFlow`]: event_loop::ControlFlow
|
||||
//! [`Exit`]: event_loop::ControlFlow::Exit
|
||||
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
//! [`WindowBuilder`]: window::WindowBuilder
|
||||
@@ -128,13 +129,14 @@
|
||||
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
|
||||
//! [`platform`]: platform
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![deny(clippy::all)]
|
||||
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
//! Types useful for interacting with a user's monitors.
|
||||
//!
|
||||
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
|
||||
//! type. This is retrieved from one of the following methods, which return an iterator of
|
||||
//! [`MonitorHandle`][monitor_handle]:
|
||||
//! - [`EventLoopWindowTarget::available_monitors`][loop_get]
|
||||
//! - [`Window::available_monitors`][window_get].
|
||||
//!
|
||||
//! [monitor_handle]: crate::monitor::MonitorHandle
|
||||
//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors
|
||||
//! [window_get]: crate::window::Window::available_monitors
|
||||
//! If you want to get basic information about a monitor, you can use the
|
||||
//! [`MonitorHandle`] type. This is retrieved from one of the following
|
||||
//! methods, which return an iterator of [`MonitorHandle`]:
|
||||
//! - [`EventLoopWindowTarget::available_monitors`](crate::event_loop::EventLoopWindowTarget::available_monitors).
|
||||
//! - [`Window::available_monitors`](crate::window::Window::available_monitors).
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
platform_impl,
|
||||
@@ -16,10 +12,7 @@ use crate::{
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with:
|
||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||
///
|
||||
/// [monitor_get]: crate::monitor::MonitorHandle::video_modes
|
||||
/// Can be acquired with [`MonitorHandle::video_modes`].
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) video_mode: platform_impl::VideoMode,
|
||||
@@ -46,8 +39,8 @@ impl Ord for VideoMode {
|
||||
self.monitor().cmp(&other.monitor()).then(
|
||||
size.cmp(&other_size)
|
||||
.then(
|
||||
self.refresh_rate()
|
||||
.cmp(&other.refresh_rate())
|
||||
self.refresh_rate_millihertz()
|
||||
.cmp(&other.refresh_rate_millihertz())
|
||||
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||
)
|
||||
.reverse(),
|
||||
@@ -75,12 +68,10 @@ impl VideoMode {
|
||||
self.video_mode.bit_depth()
|
||||
}
|
||||
|
||||
/// Returns the refresh rate of this video mode. **Note**: the returned
|
||||
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
||||
/// value to be exact.
|
||||
/// Returns the refresh rate of this video mode in mHz.
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.video_mode.refresh_rate()
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
self.video_mode.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the monitor that this video mode is valid for. Each monitor has
|
||||
@@ -95,10 +86,10 @@ impl std::fmt::Display for VideoMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}x{} @ {} Hz ({} bpp)",
|
||||
"{}x{} @ {} mHz ({} bpp)",
|
||||
self.size().width,
|
||||
self.size().height,
|
||||
self.refresh_rate(),
|
||||
self.refresh_rate_millihertz(),
|
||||
self.bit_depth()
|
||||
)
|
||||
}
|
||||
@@ -148,6 +139,15 @@ impl MonitorHandle {
|
||||
self.inner.position()
|
||||
}
|
||||
|
||||
/// The monitor refresh rate used by the system.
|
||||
///
|
||||
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
|
||||
/// enter fullscreen should be used instead.
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
self.inner.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
///
|
||||
/// See the [`dpi`](crate::dpi) module for more information.
|
||||
|
||||
@@ -7,15 +7,15 @@ use crate::{
|
||||
use ndk::configuration::Configuration;
|
||||
use ndk_glue::Rect;
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Android.
|
||||
/// Additional methods on [`EventLoop`] that are specific to Android.
|
||||
pub trait EventLoopExtAndroid {}
|
||||
|
||||
impl<T> EventLoopExtAndroid for EventLoop<T> {}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Android.
|
||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Android.
|
||||
pub trait EventLoopWindowTargetExtAndroid {}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Android.
|
||||
/// Additional methods on [`Window`] that are specific to Android.
|
||||
pub trait WindowExtAndroid {
|
||||
fn content_rect(&self) -> Rect;
|
||||
|
||||
@@ -34,7 +34,7 @@ impl WindowExtAndroid for Window {
|
||||
|
||||
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to Android.
|
||||
pub trait WindowBuilderExtAndroid {}
|
||||
|
||||
impl WindowBuilderExtAndroid for WindowBuilder {}
|
||||
|
||||
@@ -4,22 +4,21 @@ use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::get_aux_state_mut,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Additional methods on `Window` that are specific to MacOS.
|
||||
/// Additional methods on [`Window`] that are specific to MacOS.
|
||||
pub trait WindowExtMacOS {
|
||||
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn ns_window(&self) -> *mut c_void;
|
||||
|
||||
/// Returns a pointer to the cocoa `NSView` that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
fn ns_view(&self) -> *mut c_void;
|
||||
|
||||
/// Returns whether or not the window is in simple fullscreen mode.
|
||||
@@ -74,7 +73,7 @@ impl WindowExtMacOS for Window {
|
||||
}
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ActivationPolicy {
|
||||
/// Corresponds to `NSApplicationActivationPolicyRegular`.
|
||||
Regular,
|
||||
@@ -90,16 +89,14 @@ impl Default for ActivationPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to MacOS.
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to MacOS.
|
||||
///
|
||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
|
||||
/// on the base `WindowBuilder`:
|
||||
///
|
||||
/// - `with_titlebar_transparent`
|
||||
/// - `with_title_hidden`
|
||||
/// - `with_titlebar_hidden`
|
||||
/// - `with_titlebar_buttons_hidden`
|
||||
/// - `with_fullsize_content_view`
|
||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
|
||||
/// - `with_titlebar_transparent`
|
||||
/// - `with_title_hidden`
|
||||
/// - `with_titlebar_hidden`
|
||||
/// - `with_titlebar_buttons_hidden`
|
||||
/// - `with_fullsize_content_view`
|
||||
pub trait WindowBuilderExtMacOS {
|
||||
/// 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)
|
||||
@@ -162,7 +159,7 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
|
||||
#[inline]
|
||||
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
self.platform_specific.resize_increments = Some(increments);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -179,40 +176,67 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoopExtMacOS {
|
||||
/// Sets the activation policy for the application. It is set to
|
||||
/// `NSApplicationActivationPolicyRegular` by default.
|
||||
pub trait EventLoopBuilderExtMacOS {
|
||||
/// Sets the activation policy for the application.
|
||||
///
|
||||
/// This function only takes effect if it's called before calling [`run`](crate::event_loop::EventLoop::run) or
|
||||
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
|
||||
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy);
|
||||
/// It is set to [`ActivationPolicy::Regular`] by default.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the activation policy to "accessory".
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy};
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// builder.with_activation_policy(ActivationPolicy::Accessory);
|
||||
/// # if false { // We can't test this part
|
||||
/// let event_loop = builder.build();
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
|
||||
|
||||
/// Used to prevent a default menubar menu from getting created
|
||||
/// Used to control whether a default menubar menu is created.
|
||||
///
|
||||
/// The default menu creation is enabled by default.
|
||||
/// Menu creation is enabled by default.
|
||||
///
|
||||
/// This function only takes effect if it's called before calling
|
||||
/// [`run`](crate::event_loop::EventLoop::run) or
|
||||
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
|
||||
fn enable_default_menu_creation(&mut self, enable: bool);
|
||||
/// # Example
|
||||
///
|
||||
/// Disable creating a default menubar.
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "macos")]
|
||||
/// builder.with_default_menu(false);
|
||||
/// # if false { // We can't test this part
|
||||
/// let event_loop = builder.build();
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
|
||||
}
|
||||
impl<T> EventLoopExtMacOS for EventLoop<T> {
|
||||
|
||||
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
|
||||
unsafe {
|
||||
get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy;
|
||||
}
|
||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enable_default_menu_creation(&mut self, enable: bool) {
|
||||
unsafe {
|
||||
get_aux_state_mut(&**self.event_loop.delegate).create_default_menu = enable;
|
||||
}
|
||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
|
||||
self.platform_specific.default_menu = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to MacOS.
|
||||
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
|
||||
pub trait MonitorHandleExtMacOS {
|
||||
/// Returns the identifier of the monitor for Cocoa.
|
||||
fn native_id(&self) -> u32;
|
||||
@@ -231,7 +255,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
|
||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS.
|
||||
pub trait EventLoopWindowTargetExtMacOS {
|
||||
/// Hide the entire application. In most applications this is typically triggered with Command-H.
|
||||
fn hide_application(&self);
|
||||
|
||||
@@ -19,7 +19,7 @@ pub mod android;
|
||||
pub mod ios;
|
||||
pub mod macos;
|
||||
pub mod unix;
|
||||
pub mod web;
|
||||
pub mod windows;
|
||||
|
||||
pub mod run_return;
|
||||
pub mod web;
|
||||
|
||||
@@ -14,17 +14,18 @@ use crate::{
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
/// Additional methods on `EventLoop` to return control flow to the caller.
|
||||
/// Additional methods on [`EventLoop`] to return control flow to the caller.
|
||||
pub trait EventLoopExtRunReturn {
|
||||
/// A type provided by the user that can be passed through `Event::UserEvent`.
|
||||
/// A type provided by the user that can be passed through [`Event::UserEvent`].
|
||||
type UserEvent;
|
||||
|
||||
/// Initializes the `winit` event loop.
|
||||
///
|
||||
/// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns
|
||||
/// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`.
|
||||
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
|
||||
/// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`].
|
||||
///
|
||||
/// # Caveats
|
||||
///
|
||||
/// Despite its appearance at first glance, this is *not* a perfect replacement for
|
||||
/// `poll_events`. For example, this function will not return on Windows or macOS while a
|
||||
/// window is getting resized, resulting in all application logic outside of the
|
||||
@@ -33,7 +34,12 @@ pub trait EventLoopExtRunReturn {
|
||||
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
|
||||
///
|
||||
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
|
||||
fn run_return<F>(&mut self, event_handler: F)
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Wayland:** This function returns `1` upon disconnection from
|
||||
/// the display server.
|
||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
@@ -45,7 +51,7 @@ pub trait EventLoopExtRunReturn {
|
||||
impl<T> EventLoopExtRunReturn for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn run_return<F>(&mut self, event_handler: F)
|
||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
||||
where
|
||||
F: FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::os::raw;
|
||||
use std::{ptr, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
@@ -19,9 +19,9 @@ use crate::{
|
||||
#[cfg(feature = "x11")]
|
||||
use crate::dpi::Size;
|
||||
#[cfg(feature = "x11")]
|
||||
use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection};
|
||||
use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS};
|
||||
use crate::platform_impl::{
|
||||
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
||||
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
||||
Window as LinuxWindow,
|
||||
};
|
||||
|
||||
@@ -32,13 +32,41 @@ pub use crate::platform_impl::x11;
|
||||
#[cfg(feature = "x11")]
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub use crate::window::Theme;
|
||||
|
||||
/// The first argument in the provided hook will be the pointer to `XDisplay`
|
||||
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
|
||||
/// indicator whether the error was handled by the callback.
|
||||
///
|
||||
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
|
||||
#[cfg(feature = "x11")]
|
||||
pub type XlibErrorHook =
|
||||
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
|
||||
|
||||
/// Hook to winit's xlib error handling callback.
|
||||
///
|
||||
/// This method is provided as a safe way to handle the errors comming from X11 when using xlib
|
||||
/// in external crates, like glutin for GLX access. Trying to handle errors by speculating with
|
||||
/// `XSetErrorHandler` is [`unsafe`].
|
||||
///
|
||||
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
|
||||
// Append new hook.
|
||||
unsafe {
|
||||
XLIB_ERROR_HOOKS.lock().push(hook);
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Unix.
|
||||
pub trait EventLoopWindowTargetExtUnix {
|
||||
/// True if the `EventLoopWindowTarget` uses Wayland.
|
||||
/// True if the [`EventLoopWindowTarget`] uses Wayland.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn is_wayland(&self) -> bool;
|
||||
|
||||
/// True if the `EventLoopWindowTarget` uses X11.
|
||||
/// True if the [`EventLoopWindowTarget`] uses X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn is_x11(&self) -> bool;
|
||||
|
||||
@@ -47,11 +75,13 @@ pub trait EventLoopWindowTargetExtUnix {
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
||||
|
||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this
|
||||
/// `EventLoopWindowTarget`.
|
||||
/// [`EventLoopWindowTarget`].
|
||||
///
|
||||
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
|
||||
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the winit `EventLoop` is destroyed.
|
||||
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
|
||||
///
|
||||
/// [`EventLoop`]: crate::event_loop::EventLoop
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
}
|
||||
@@ -70,7 +100,6 @@ impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.p {
|
||||
@@ -93,106 +122,48 @@ impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Unix.
|
||||
pub trait EventLoopExtUnix {
|
||||
/// Builds a new `EventLoop` that is forced to use X11.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If called outside the main thread. To initialize an X11 event loop outside
|
||||
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
|
||||
/// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
|
||||
pub trait EventLoopBuilderExtUnix {
|
||||
/// Force using X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn new_x11() -> Result<Self, XNotSupported>
|
||||
where
|
||||
Self: Sized;
|
||||
fn with_x11(&mut self) -> &mut Self;
|
||||
|
||||
/// Builds a new `EventLoop` that is forced to use Wayland.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If called outside the main thread. To initialize a Wayland event loop outside
|
||||
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
|
||||
/// Force using Wayland.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn new_wayland() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn with_wayland(&mut self) -> &mut Self;
|
||||
|
||||
/// Builds a new `EventLoop` on any thread.
|
||||
/// Whether to allow the event loop to be created off of the main thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
fn new_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new X11 `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
#[cfg(feature = "x11")]
|
||||
fn new_x11_any_thread() -> Result<Self, XNotSupported>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Builds a new Wayland `EventLoop` on any thread.
|
||||
///
|
||||
/// This method bypasses the cross-platform compatibility requirement
|
||||
/// that `EventLoop` be created on the main thread.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn new_wayland_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
/// 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;
|
||||
}
|
||||
|
||||
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop,
|
||||
_marker: std::marker::PhantomData,
|
||||
impl<T> EventLoopBuilderExtUnix for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11(&mut self) -> &mut Self {
|
||||
self.platform_specific.forced_backend = Some(Backend::X);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_wayland(&mut self) -> &mut Self {
|
||||
self.platform_specific.forced_backend = Some(Backend::Wayland);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
|
||||
self.platform_specific.any_thread = any_thread;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtUnix for EventLoop<T> {
|
||||
#[inline]
|
||||
fn new_any_thread() -> Self {
|
||||
wrap_ev(LinuxEventLoop::new_any_thread())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn new_wayland_any_thread() -> Self {
|
||||
wrap_ev(
|
||||
LinuxEventLoop::new_wayland_any_thread()
|
||||
// TODO: propagate
|
||||
.expect("failed to open Wayland connection"),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn new_x11() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11().map(wrap_ev)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn new_wayland() -> Self {
|
||||
wrap_ev(
|
||||
LinuxEventLoop::new_wayland()
|
||||
// TODO: propagate
|
||||
.expect("failed to open Wayland connection"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Unix.
|
||||
/// Additional methods on [`Window`] that are specific to Unix.
|
||||
pub trait WindowExtUnix {
|
||||
/// Returns the ID of the `Window` xlib object that is used by this window.
|
||||
/// Returns the ID of the [`Window`] xlib object that is used by this window.
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
#[cfg(feature = "x11")]
|
||||
@@ -202,7 +173,7 @@ pub trait WindowExtUnix {
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
@@ -217,7 +188,7 @@ pub trait WindowExtUnix {
|
||||
///
|
||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "x11")]
|
||||
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
@@ -225,7 +196,7 @@ pub trait WindowExtUnix {
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
@@ -233,16 +204,23 @@ pub trait WindowExtUnix {
|
||||
///
|
||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
||||
///
|
||||
/// The pointer will become invalid when the glutin `Window` is destroyed.
|
||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Updates [`Theme`] of window decorations.
|
||||
///
|
||||
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
|
||||
/// Possible values for env variable are: "dark" and light"
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_set_csd_theme(&self, config: Theme);
|
||||
|
||||
/// Check if the window is ready for drawing
|
||||
///
|
||||
/// It is a remnant of a previous implementation detail for the
|
||||
/// wayland backend, and is no longer relevant.
|
||||
///
|
||||
/// Always return true.
|
||||
/// Always return `true`.
|
||||
#[deprecated]
|
||||
fn is_ready(&self) -> bool;
|
||||
}
|
||||
@@ -279,7 +257,6 @@ impl WindowExtUnix for Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "x11")]
|
||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||
match self.window {
|
||||
@@ -319,45 +296,90 @@ impl WindowExtUnix for Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn wayland_set_csd_theme(&self, theme: Theme) {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_ready(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to Unix.
|
||||
pub trait WindowBuilderExtUnix {
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11_screen(self, screen_id: i32) -> Self;
|
||||
|
||||
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_class(self, class: String, instance: String) -> Self;
|
||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_override_redirect(self, override_redirect: bool) -> Self;
|
||||
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
|
||||
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
||||
/// Build window with resize increment hint. Only implemented on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
||||
|
||||
/// Build window with a given application ID. It should match the `.desktop` file distributed with
|
||||
/// your program. Only relevant on Wayland.
|
||||
/// Build window with the given `general` and `instance` names.
|
||||
///
|
||||
/// On Wayland, the `general` name sets an application ID, which should match the `.desktop`
|
||||
/// file destributed with your program. The `instance` is a `no-op`.
|
||||
///
|
||||
/// On X11, the `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_override_redirect(self, override_redirect: bool) -> Self;
|
||||
|
||||
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
|
||||
|
||||
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
||||
|
||||
/// Build window with certain decoration [`Theme`]
|
||||
///
|
||||
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
|
||||
/// Possible values for env variable are: "dark" and light"
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_app_id(self, app_id: String) -> Self;
|
||||
fn with_wayland_csd_theme(self, theme: Theme) -> Self;
|
||||
|
||||
/// Build window with resize increment hint. Only implemented on X11.
|
||||
///
|
||||
/// ```
|
||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
/// # use winit::window::WindowBuilder;
|
||||
/// # use winit::platform::unix::WindowBuilderExtUnix;
|
||||
/// // Specify the size in logical dimensions like this:
|
||||
/// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0));
|
||||
///
|
||||
/// // Or specify the size in physical dimensions like this:
|
||||
/// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200));
|
||||
/// ```
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
|
||||
|
||||
/// Build window with base size hint. Only implemented on X11.
|
||||
///
|
||||
/// ```
|
||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
/// # use winit::window::WindowBuilder;
|
||||
/// # use winit::platform::unix::WindowBuilderExtUnix;
|
||||
/// // Specify the size in logical dimensions like this:
|
||||
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
|
||||
///
|
||||
/// // Or specify the size in physical dimensions like this:
|
||||
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
|
||||
/// ```
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtUnix for WindowBuilder {
|
||||
@@ -379,9 +401,8 @@ impl WindowBuilderExtUnix for WindowBuilder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_class(mut self, instance: String, class: String) -> Self {
|
||||
self.platform_specific.class = Some((instance, class));
|
||||
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
|
||||
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -406,6 +427,13 @@ impl WindowBuilderExtUnix for WindowBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_wayland_csd_theme(mut self, theme: Theme) -> Self {
|
||||
self.platform_specific.csd_theme = Some(theme);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "x11")]
|
||||
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
|
||||
@@ -419,13 +447,6 @@ impl WindowBuilderExtUnix for WindowBuilder {
|
||||
self.platform_specific.base_size = Some(base_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland")]
|
||||
fn with_app_id(mut self, app_id: String) -> Self {
|
||||
self.platform_specific.app_id = Some(app_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to Linux.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
//! The web target does not automatically insert the canvas element object into the web page, to
|
||||
//! allow end users to determine how the page should be laid out. Use the `WindowExtWebSys` trait
|
||||
//! to retrieve the canvas from the Window. Alternatively, use the `WindowBuilderExtWebSys` trait
|
||||
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
|
||||
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
|
||||
//! to provide your own canvas.
|
||||
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::window::WindowBuilder;
|
||||
|
||||
use web_sys::HtmlCanvasElement;
|
||||
@@ -18,6 +22,17 @@ pub trait WindowExtWebSys {
|
||||
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
||||
|
||||
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
|
||||
/// when appropriate.
|
||||
///
|
||||
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
|
||||
/// the default behavior of scrolling the page.
|
||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
||||
|
||||
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
|
||||
/// canvas keyboard events.
|
||||
fn with_focusable(self, focusable: bool) -> Self;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
@@ -26,4 +41,51 @@ impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
|
||||
self.platform_specific.prevent_default = prevent_default;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn with_focusable(mut self, focusable: bool) -> Self {
|
||||
self.platform_specific.focusable = focusable;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to the web.
|
||||
pub trait EventLoopExtWebSys {
|
||||
/// A type provided by the user that can be passed through `Event::UserEvent`.
|
||||
type UserEvent;
|
||||
|
||||
/// Initializes the winit event loop.
|
||||
///
|
||||
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its `!` return type.
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
);
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
type UserEvent = T;
|
||||
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
+ FnMut(
|
||||
Event<'_, Self::UserEvent>,
|
||||
&EventLoopWindowTarget<Self::UserEvent>,
|
||||
&mut ControlFlow,
|
||||
),
|
||||
{
|
||||
self.event_loop.spawn(event_handler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,124 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use std::path::Path;
|
||||
|
||||
use winapi::shared::minwindef::WORD;
|
||||
use winapi::shared::windef::{HMENU, HWND};
|
||||
use std::{ffi::c_void, path::Path};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::DeviceId,
|
||||
event_loop::EventLoop,
|
||||
event_loop::EventLoopBuilder,
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon},
|
||||
platform_impl::{Parent, WinIcon},
|
||||
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Window Handle type used by Win32 API
|
||||
pub type HWND = isize;
|
||||
/// Menu Handle type used by Win32 API
|
||||
pub type HMENU = isize;
|
||||
/// Monitor Handle type used by Win32 API
|
||||
pub type HMONITOR = isize;
|
||||
/// Instance Handle type used by Win32 API
|
||||
pub type HINSTANCE = isize;
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
pub trait EventLoopExtWindows {
|
||||
/// Creates an event loop off of the main thread.
|
||||
pub trait EventLoopBuilderExtWindows {
|
||||
/// 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.
|
||||
///
|
||||
/// # `Window` caveats
|
||||
///
|
||||
/// Note that any `Window` created on the new thread will be destroyed when the thread
|
||||
/// terminates. Attempting to use a `Window` after its parent thread terminates has
|
||||
/// unspecified, although explicitly not undefined, behavior.
|
||||
fn new_any_thread() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
|
||||
|
||||
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
|
||||
/// undesirable, you can create an `EventLoop` using this function instead.
|
||||
fn new_dpi_unaware() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Creates a DPI-unaware event loop off of the main thread.
|
||||
/// Whether to enable process-wide DPI awareness.
|
||||
///
|
||||
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here.
|
||||
fn new_dpi_unaware_any_thread() -> Self
|
||||
/// By default, `winit` will attempt to enable process-wide DPI awareness. If
|
||||
/// that's undesirable, you can disable it with this function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Disable process-wide DPI awareness.
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// builder.with_dpi_aware(false);
|
||||
/// # if false { // We can't test this part
|
||||
/// let event_loop = builder.build();
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self;
|
||||
|
||||
/// A callback to be executed before dispatching a win32 message to the window procedure.
|
||||
/// Return true to disable winit's internal message dispatching.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG};
|
||||
/// use winit::event_loop::EventLoopBuilder;
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
||||
///
|
||||
/// let mut builder = EventLoopBuilder::new();
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// builder.with_msg_hook(|msg|{
|
||||
/// let msg = msg as *const MSG;
|
||||
/// # let accels: Vec<ACCEL> = Vec::new();
|
||||
/// let translated = unsafe {
|
||||
/// TranslateAcceleratorW(
|
||||
/// (*msg).hwnd,
|
||||
/// CreateAcceleratorTableW(accels.as_ptr() as _, 1),
|
||||
/// msg,
|
||||
/// ) == 1
|
||||
/// };
|
||||
/// translated
|
||||
/// });
|
||||
/// ```
|
||||
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
|
||||
where
|
||||
Self: Sized;
|
||||
F: FnMut(*const c_void) -> bool + 'static;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWindows for EventLoop<T> {
|
||||
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
fn new_any_thread() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_any_thread(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
|
||||
self.platform_specific.any_thread = any_thread;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_dpi_unaware() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_dpi_unaware(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
|
||||
self.platform_specific.dpi_aware = dpi_aware;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_dpi_unaware_any_thread() -> Self {
|
||||
EventLoop {
|
||||
event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
|
||||
where
|
||||
F: FnMut(*const c_void) -> bool + 'static,
|
||||
{
|
||||
self.platform_specific.msg_hook = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExtWindows {
|
||||
/// Returns the HINSTANCE of the window
|
||||
fn hinstance(&self) -> *mut c_void;
|
||||
fn hinstance(&self) -> HINSTANCE;
|
||||
/// Returns the native handle that is used by this window.
|
||||
///
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn hwnd(&self) -> *mut c_void;
|
||||
fn hwnd(&self) -> HWND;
|
||||
|
||||
/// Enables or disables mouse and keyboard input to the specified window.
|
||||
///
|
||||
@@ -97,17 +140,20 @@ pub trait WindowExtWindows {
|
||||
|
||||
/// Returns the current window theme.
|
||||
fn theme(&self) -> Theme;
|
||||
|
||||
/// Whether to show or hide the window icon in the taskbar.
|
||||
fn set_skip_taskbar(&self, skip: bool);
|
||||
}
|
||||
|
||||
impl WindowExtWindows for Window {
|
||||
#[inline]
|
||||
fn hinstance(&self) -> *mut c_void {
|
||||
self.window.hinstance() as *mut _
|
||||
fn hinstance(&self) -> HINSTANCE {
|
||||
self.window.hinstance()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hwnd(&self) -> *mut c_void {
|
||||
self.window.hwnd() as *mut _
|
||||
fn hwnd(&self) -> HWND {
|
||||
self.window.hwnd()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -124,6 +170,11 @@ impl WindowExtWindows for Window {
|
||||
fn theme(&self) -> Theme {
|
||||
self.window.theme()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_skip_taskbar(&self, skip: bool) {
|
||||
self.window.set_skip_taskbar(skip)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
@@ -151,10 +202,12 @@ pub trait WindowBuilderExtWindows {
|
||||
///
|
||||
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
|
||||
///
|
||||
/// The menu must have been manually created beforehand with [`winapi::um::winuser::CreateMenu`] or similar.
|
||||
/// The menu must have been manually created beforehand with [`CreateMenu`] or similar.
|
||||
///
|
||||
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
|
||||
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
||||
///
|
||||
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
|
||||
fn with_menu(self, menu: HMENU) -> WindowBuilder;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
@@ -173,6 +226,9 @@ pub trait WindowBuilderExtWindows {
|
||||
|
||||
/// Forces a theme or uses the system settings if `None` was provided.
|
||||
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
|
||||
|
||||
/// Whether show or hide the window icon in the taskbar.
|
||||
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtWindows for WindowBuilder {
|
||||
@@ -217,6 +273,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
|
||||
self.platform_specific.preferred_theme = theme;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
|
||||
self.platform_specific.skip_taskbar = skip;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to Windows.
|
||||
@@ -225,7 +287,7 @@ pub trait MonitorHandleExtWindows {
|
||||
fn native_id(&self) -> String;
|
||||
|
||||
/// Returns the handle of the monitor - `HMONITOR`.
|
||||
fn hmonitor(&self) -> *mut c_void;
|
||||
fn hmonitor(&self) -> HMONITOR;
|
||||
}
|
||||
|
||||
impl MonitorHandleExtWindows for MonitorHandle {
|
||||
@@ -235,8 +297,8 @@ impl MonitorHandleExtWindows for MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hmonitor(&self) -> *mut c_void {
|
||||
self.inner.hmonitor() as *mut _
|
||||
fn hmonitor(&self) -> HMONITOR {
|
||||
self.inner.hmonitor()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +336,7 @@ pub trait IconExtWindows: Sized {
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
}
|
||||
|
||||
impl IconExtWindows for Icon {
|
||||
@@ -286,7 +348,7 @@ impl IconExtWindows for Icon {
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
#![cfg(target_os = "android")]
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error, event,
|
||||
event_loop::{self, ControlFlow},
|
||||
monitor, window,
|
||||
};
|
||||
use ndk::{
|
||||
configuration::Configuration,
|
||||
event::{InputEvent, KeyAction, MotionAction},
|
||||
looper::{ForeignLooper, Poll, ThreadLooper},
|
||||
};
|
||||
use ndk_glue::{Event, Rect};
|
||||
use raw_window_handle::{AndroidNdkHandle, RawWindowHandle};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
sync::{mpsc, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::from_asset_manager(
|
||||
&ndk_glue::native_activity().asset_manager()
|
||||
));
|
||||
// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event
|
||||
// contained in the `Option`. The event is moved outside of the `Option` replacing it with a
|
||||
// `None`.
|
||||
//
|
||||
// This allows us to inject event into the event loop without going through `ndk-glue` and
|
||||
// calling unsafe function that should only be called by Android.
|
||||
static ref INTERNAL_EVENT: RwLock<Option<InternalEvent>> = RwLock::new(None);
|
||||
}
|
||||
use ndk::{
|
||||
configuration::Configuration,
|
||||
event::{InputEvent, KeyAction, Keycode, MotionAction},
|
||||
looper::{ForeignLooper, Poll, ThreadLooper},
|
||||
native_window::NativeWindow,
|
||||
};
|
||||
use ndk_glue::{Event, LockReadGuard, Rect};
|
||||
use once_cell::sync::Lazy;
|
||||
use raw_window_handle::{
|
||||
AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
event::{self, VirtualKeyCode},
|
||||
event_loop::{self, ControlFlow},
|
||||
monitor,
|
||||
window::{self, CursorGrabMode},
|
||||
};
|
||||
|
||||
static CONFIG: Lazy<RwLock<Configuration>> = Lazy::new(|| {
|
||||
RwLock::new(Configuration::from_asset_manager(
|
||||
#[allow(deprecated)] // TODO: rust-windowing/winit#2196
|
||||
&ndk_glue::native_activity().asset_manager(),
|
||||
))
|
||||
});
|
||||
// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event
|
||||
// contained in the `Option`. The event is moved outside of the `Option` replacing it with a
|
||||
// `None`.
|
||||
//
|
||||
// This allows us to inject event into the event loop without going through `ndk-glue` and
|
||||
// calling unsafe function that should only be called by Android.
|
||||
static INTERNAL_EVENT: Lazy<RwLock<Option<InternalEvent>>> = Lazy::new(|| RwLock::new(None));
|
||||
|
||||
enum InternalEvent {
|
||||
RedrawRequested,
|
||||
@@ -43,6 +52,170 @@ enum EventSource {
|
||||
Internal(InternalEvent),
|
||||
}
|
||||
|
||||
fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option<event::VirtualKeyCode> {
|
||||
match keycode {
|
||||
Keycode::A => Some(VirtualKeyCode::A),
|
||||
Keycode::B => Some(VirtualKeyCode::B),
|
||||
Keycode::C => Some(VirtualKeyCode::C),
|
||||
Keycode::D => Some(VirtualKeyCode::D),
|
||||
Keycode::E => Some(VirtualKeyCode::E),
|
||||
Keycode::F => Some(VirtualKeyCode::F),
|
||||
Keycode::G => Some(VirtualKeyCode::G),
|
||||
Keycode::H => Some(VirtualKeyCode::H),
|
||||
Keycode::I => Some(VirtualKeyCode::I),
|
||||
Keycode::J => Some(VirtualKeyCode::J),
|
||||
Keycode::K => Some(VirtualKeyCode::K),
|
||||
Keycode::L => Some(VirtualKeyCode::L),
|
||||
Keycode::M => Some(VirtualKeyCode::M),
|
||||
Keycode::N => Some(VirtualKeyCode::N),
|
||||
Keycode::O => Some(VirtualKeyCode::O),
|
||||
Keycode::P => Some(VirtualKeyCode::P),
|
||||
Keycode::Q => Some(VirtualKeyCode::Q),
|
||||
Keycode::R => Some(VirtualKeyCode::R),
|
||||
Keycode::S => Some(VirtualKeyCode::S),
|
||||
Keycode::T => Some(VirtualKeyCode::T),
|
||||
Keycode::U => Some(VirtualKeyCode::U),
|
||||
Keycode::V => Some(VirtualKeyCode::V),
|
||||
Keycode::W => Some(VirtualKeyCode::W),
|
||||
Keycode::X => Some(VirtualKeyCode::X),
|
||||
Keycode::Y => Some(VirtualKeyCode::Y),
|
||||
Keycode::Z => Some(VirtualKeyCode::Z),
|
||||
|
||||
Keycode::Keycode0 => Some(VirtualKeyCode::Key0),
|
||||
Keycode::Keycode1 => Some(VirtualKeyCode::Key1),
|
||||
Keycode::Keycode2 => Some(VirtualKeyCode::Key2),
|
||||
Keycode::Keycode3 => Some(VirtualKeyCode::Key3),
|
||||
Keycode::Keycode4 => Some(VirtualKeyCode::Key4),
|
||||
Keycode::Keycode5 => Some(VirtualKeyCode::Key5),
|
||||
Keycode::Keycode6 => Some(VirtualKeyCode::Key6),
|
||||
Keycode::Keycode7 => Some(VirtualKeyCode::Key7),
|
||||
Keycode::Keycode8 => Some(VirtualKeyCode::Key8),
|
||||
Keycode::Keycode9 => Some(VirtualKeyCode::Key9),
|
||||
|
||||
Keycode::Numpad0 => Some(VirtualKeyCode::Numpad0),
|
||||
Keycode::Numpad1 => Some(VirtualKeyCode::Numpad1),
|
||||
Keycode::Numpad2 => Some(VirtualKeyCode::Numpad2),
|
||||
Keycode::Numpad3 => Some(VirtualKeyCode::Numpad3),
|
||||
Keycode::Numpad4 => Some(VirtualKeyCode::Numpad4),
|
||||
Keycode::Numpad5 => Some(VirtualKeyCode::Numpad5),
|
||||
Keycode::Numpad6 => Some(VirtualKeyCode::Numpad6),
|
||||
Keycode::Numpad7 => Some(VirtualKeyCode::Numpad7),
|
||||
Keycode::Numpad8 => Some(VirtualKeyCode::Numpad8),
|
||||
Keycode::Numpad9 => Some(VirtualKeyCode::Numpad9),
|
||||
|
||||
Keycode::NumpadAdd => Some(VirtualKeyCode::NumpadAdd),
|
||||
Keycode::NumpadSubtract => Some(VirtualKeyCode::NumpadSubtract),
|
||||
Keycode::NumpadMultiply => Some(VirtualKeyCode::NumpadMultiply),
|
||||
Keycode::NumpadDivide => Some(VirtualKeyCode::NumpadDivide),
|
||||
Keycode::NumpadEnter => Some(VirtualKeyCode::NumpadEnter),
|
||||
Keycode::NumpadEquals => Some(VirtualKeyCode::NumpadEquals),
|
||||
Keycode::NumpadComma => Some(VirtualKeyCode::NumpadComma),
|
||||
Keycode::NumpadDot => Some(VirtualKeyCode::NumpadDecimal),
|
||||
Keycode::NumLock => Some(VirtualKeyCode::Numlock),
|
||||
|
||||
Keycode::DpadLeft => Some(VirtualKeyCode::Left),
|
||||
Keycode::DpadRight => Some(VirtualKeyCode::Right),
|
||||
Keycode::DpadUp => Some(VirtualKeyCode::Up),
|
||||
Keycode::DpadDown => Some(VirtualKeyCode::Down),
|
||||
|
||||
Keycode::F1 => Some(VirtualKeyCode::F1),
|
||||
Keycode::F2 => Some(VirtualKeyCode::F2),
|
||||
Keycode::F3 => Some(VirtualKeyCode::F3),
|
||||
Keycode::F4 => Some(VirtualKeyCode::F4),
|
||||
Keycode::F5 => Some(VirtualKeyCode::F5),
|
||||
Keycode::F6 => Some(VirtualKeyCode::F6),
|
||||
Keycode::F7 => Some(VirtualKeyCode::F7),
|
||||
Keycode::F8 => Some(VirtualKeyCode::F8),
|
||||
Keycode::F9 => Some(VirtualKeyCode::F9),
|
||||
Keycode::F10 => Some(VirtualKeyCode::F10),
|
||||
Keycode::F11 => Some(VirtualKeyCode::F11),
|
||||
Keycode::F12 => Some(VirtualKeyCode::F12),
|
||||
|
||||
Keycode::Space => Some(VirtualKeyCode::Space),
|
||||
Keycode::Escape => Some(VirtualKeyCode::Escape),
|
||||
Keycode::Enter => Some(VirtualKeyCode::Return), // not on the Numpad
|
||||
Keycode::Tab => Some(VirtualKeyCode::Tab),
|
||||
|
||||
Keycode::PageUp => Some(VirtualKeyCode::PageUp),
|
||||
Keycode::PageDown => Some(VirtualKeyCode::PageDown),
|
||||
Keycode::MoveHome => Some(VirtualKeyCode::Home),
|
||||
Keycode::MoveEnd => Some(VirtualKeyCode::End),
|
||||
Keycode::Insert => Some(VirtualKeyCode::Insert),
|
||||
|
||||
Keycode::Del => Some(VirtualKeyCode::Back), // Backspace (above Enter)
|
||||
Keycode::ForwardDel => Some(VirtualKeyCode::Delete), // Delete (below Insert)
|
||||
|
||||
Keycode::Copy => Some(VirtualKeyCode::Copy),
|
||||
Keycode::Paste => Some(VirtualKeyCode::Paste),
|
||||
Keycode::Cut => Some(VirtualKeyCode::Cut),
|
||||
|
||||
Keycode::VolumeUp => Some(VirtualKeyCode::VolumeUp),
|
||||
Keycode::VolumeDown => Some(VirtualKeyCode::VolumeDown),
|
||||
Keycode::VolumeMute => Some(VirtualKeyCode::Mute), // ???
|
||||
Keycode::Mute => Some(VirtualKeyCode::Mute), // ???
|
||||
Keycode::MediaPlayPause => Some(VirtualKeyCode::PlayPause),
|
||||
Keycode::MediaStop => Some(VirtualKeyCode::MediaStop), // ??? simple "Stop"?
|
||||
Keycode::MediaNext => Some(VirtualKeyCode::NextTrack),
|
||||
Keycode::MediaPrevious => Some(VirtualKeyCode::PrevTrack),
|
||||
|
||||
Keycode::Plus => Some(VirtualKeyCode::Plus),
|
||||
Keycode::Minus => Some(VirtualKeyCode::Minus),
|
||||
Keycode::Equals => Some(VirtualKeyCode::Equals),
|
||||
Keycode::Semicolon => Some(VirtualKeyCode::Semicolon),
|
||||
Keycode::Slash => Some(VirtualKeyCode::Slash),
|
||||
Keycode::Backslash => Some(VirtualKeyCode::Backslash),
|
||||
Keycode::Comma => Some(VirtualKeyCode::Comma),
|
||||
Keycode::Period => Some(VirtualKeyCode::Period),
|
||||
Keycode::Apostrophe => Some(VirtualKeyCode::Apostrophe),
|
||||
Keycode::Grave => Some(VirtualKeyCode::Grave),
|
||||
Keycode::At => Some(VirtualKeyCode::At),
|
||||
|
||||
// TODO: Maybe mapping this to Snapshot makes more sense? See: "PrtScr/SysRq"
|
||||
Keycode::Sysrq => Some(VirtualKeyCode::Sysrq),
|
||||
// These are usually the same (Pause/Break)
|
||||
Keycode::Break => Some(VirtualKeyCode::Pause),
|
||||
// These are exactly the same
|
||||
Keycode::ScrollLock => Some(VirtualKeyCode::Scroll),
|
||||
|
||||
Keycode::Yen => Some(VirtualKeyCode::Yen),
|
||||
Keycode::Kana => Some(VirtualKeyCode::Kana),
|
||||
|
||||
Keycode::CtrlLeft => Some(VirtualKeyCode::LControl),
|
||||
Keycode::CtrlRight => Some(VirtualKeyCode::RControl),
|
||||
|
||||
Keycode::ShiftLeft => Some(VirtualKeyCode::LShift),
|
||||
Keycode::ShiftRight => Some(VirtualKeyCode::RShift),
|
||||
|
||||
Keycode::AltLeft => Some(VirtualKeyCode::LAlt),
|
||||
Keycode::AltRight => Some(VirtualKeyCode::RAlt),
|
||||
|
||||
// Different names for the same keys
|
||||
Keycode::MetaLeft => Some(VirtualKeyCode::LWin),
|
||||
Keycode::MetaRight => Some(VirtualKeyCode::RWin),
|
||||
|
||||
Keycode::LeftBracket => Some(VirtualKeyCode::LBracket),
|
||||
Keycode::RightBracket => Some(VirtualKeyCode::RBracket),
|
||||
|
||||
Keycode::Power => Some(VirtualKeyCode::Power),
|
||||
Keycode::Sleep => Some(VirtualKeyCode::Sleep), // what about SoftSleep?
|
||||
Keycode::Wakeup => Some(VirtualKeyCode::Wake),
|
||||
|
||||
Keycode::NavigateNext => Some(VirtualKeyCode::NavigateForward),
|
||||
Keycode::NavigatePrevious => Some(VirtualKeyCode::NavigateBackward),
|
||||
|
||||
Keycode::Calculator => Some(VirtualKeyCode::Calculator),
|
||||
Keycode::Explorer => Some(VirtualKeyCode::MyComputer), // "close enough"
|
||||
Keycode::Envelope => Some(VirtualKeyCode::Mail), // "close enough"
|
||||
|
||||
Keycode::Star => Some(VirtualKeyCode::Asterisk), // ???
|
||||
Keycode::AllApps => Some(VirtualKeyCode::Apps), // ???
|
||||
Keycode::AppSwitch => Some(VirtualKeyCode::Apps), // ???
|
||||
Keycode::Refresh => Some(VirtualKeyCode::WebRefresh), // ???
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(poll: Poll) -> Option<EventSource> {
|
||||
match poll {
|
||||
Poll::Event { ident, .. } => match ident {
|
||||
@@ -64,25 +237,31 @@ fn poll(poll: Poll) -> Option<EventSource> {
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
window_target: event_loop::EventLoopWindowTarget<T>,
|
||||
user_queue: Arc<Mutex<VecDeque<T>>>,
|
||||
user_events_sender: mpsc::Sender<T>,
|
||||
user_events_receiver: mpsc::Receiver<T>,
|
||||
first_event: Option<EventSource>,
|
||||
start_cause: event::StartCause,
|
||||
looper: ThreadLooper,
|
||||
running: bool,
|
||||
window_lock: Option<LockReadGuard<NativeWindow>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
||||
|
||||
macro_rules! call_event_handler {
|
||||
( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{
|
||||
if $cf != ControlFlow::Exit {
|
||||
$event_handler($event, $window_target, &mut $cf);
|
||||
if let ControlFlow::ExitWithCode(code) = $cf {
|
||||
$event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code));
|
||||
} else {
|
||||
$event_handler($event, $window_target, &mut ControlFlow::Exit);
|
||||
$event_handler($event, $window_target, &mut $cf);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self {
|
||||
let (user_events_sender, user_events_receiver) = mpsc::channel();
|
||||
Self {
|
||||
window_target: event_loop::EventLoopWindowTarget {
|
||||
p: EventLoopWindowTarget {
|
||||
@@ -90,11 +269,13 @@ impl<T: 'static> EventLoop<T> {
|
||||
},
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
user_queue: Default::default(),
|
||||
user_events_sender,
|
||||
user_events_receiver,
|
||||
first_event: None,
|
||||
start_cause: event::StartCause::Init,
|
||||
looper: ThreadLooper::for_thread().unwrap(),
|
||||
running: false,
|
||||
window_lock: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,11 +284,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
F: 'static
|
||||
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
self.run_return(event_handler);
|
||||
::std::process::exit(0);
|
||||
let exit_code = self.run_return(event_handler);
|
||||
::std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, mut event_handler: F)
|
||||
pub fn run_return<F>(&mut self, mut event_handler: F) -> i32
|
||||
where
|
||||
F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
@@ -127,26 +308,47 @@ impl<T: 'static> EventLoop<T> {
|
||||
match self.first_event.take() {
|
||||
Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() {
|
||||
Event::WindowCreated => {
|
||||
call_event_handler!(
|
||||
event_handler,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
event::Event::Resumed
|
||||
);
|
||||
// Acquire a lock on the window to prevent Android from destroying
|
||||
// it until we've notified and waited for the user in Event::Suspended.
|
||||
// WARNING: ndk-glue is inherently racy (https://github.com/rust-windowing/winit/issues/2293)
|
||||
// and may have already received onNativeWindowDestroyed while this thread hasn't yet processed
|
||||
// the event, and would see a `None` lock+window in that case.
|
||||
if let Some(next_window_lock) = ndk_glue::native_window() {
|
||||
assert!(
|
||||
self.window_lock.replace(next_window_lock).is_none(),
|
||||
"Received `Event::WindowCreated` while we were already holding a lock"
|
||||
);
|
||||
call_event_handler!(
|
||||
event_handler,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
event::Event::Resumed
|
||||
);
|
||||
} else {
|
||||
warn!("Received `Event::WindowCreated` while `ndk_glue::native_window()` provides no window");
|
||||
}
|
||||
}
|
||||
Event::WindowResized => resized = true,
|
||||
Event::WindowRedrawNeeded => redraw = true,
|
||||
Event::WindowDestroyed => {
|
||||
call_event_handler!(
|
||||
event_handler,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
event::Event::Suspended
|
||||
);
|
||||
// Release the lock, allowing Android to clean up this surface
|
||||
// WARNING: See above - if ndk-glue is racy, this event may be called
|
||||
// without having a `self.window_lock` in place.
|
||||
if self.window_lock.take().is_some() {
|
||||
call_event_handler!(
|
||||
event_handler,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
event::Event::Suspended
|
||||
);
|
||||
} else {
|
||||
warn!("Received `Event::WindowDestroyed` while we were not holding a window lock");
|
||||
}
|
||||
}
|
||||
Event::Pause => self.running = false,
|
||||
Event::Resume => self.running = true,
|
||||
Event::ConfigChanged => {
|
||||
#[allow(deprecated)] // TODO: rust-windowing/winit#2196
|
||||
let am = ndk_glue::native_activity().asset_manager();
|
||||
let config = Configuration::from_asset_manager(&am);
|
||||
let old_scale_factor = MonitorHandle.scale_factor();
|
||||
@@ -195,7 +397,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
},
|
||||
Some(EventSource::InputQueue) => {
|
||||
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
|
||||
while let Some(event) = input_queue.get_event() {
|
||||
while let Some(event) = input_queue.get_event().expect("get_event") {
|
||||
if let Some(event) = input_queue.pre_dispatch(event) {
|
||||
let mut handled = true;
|
||||
let window_id = window::WindowId(WindowId);
|
||||
@@ -274,7 +476,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
input: event::KeyboardInput {
|
||||
scancode: key.scan_code() as u32,
|
||||
state,
|
||||
virtual_keycode: None,
|
||||
virtual_keycode: ndk_keycode_to_virtualkeycode(
|
||||
key.key_code(),
|
||||
),
|
||||
modifiers: event::ModifiersState::default(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
@@ -294,8 +498,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
Some(EventSource::User) => {
|
||||
let mut user_queue = self.user_queue.lock().unwrap();
|
||||
while let Some(event) = user_queue.pop_front() {
|
||||
// try_recv only errors when empty (expected) or disconnect. But because Self
|
||||
// contains a Sender it will never disconnect, so no error handling need.
|
||||
while let Ok(event) = self.user_events_receiver.try_recv() {
|
||||
call_event_handler!(
|
||||
event_handler,
|
||||
self.window_target(),
|
||||
@@ -339,7 +544,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
);
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::Exit => {
|
||||
ControlFlow::ExitWithCode(code) => {
|
||||
self.first_event = poll(
|
||||
self.looper
|
||||
.poll_once_timeout(Duration::from_millis(0))
|
||||
@@ -349,7 +554,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
};
|
||||
break 'event_loop;
|
||||
break 'event_loop code;
|
||||
}
|
||||
ControlFlow::Poll => {
|
||||
self.first_event = poll(
|
||||
@@ -396,20 +601,22 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
queue: self.user_queue.clone(),
|
||||
user_events_sender: self.user_events_sender.clone(),
|
||||
looper: ForeignLooper::for_thread().expect("called from event loop thread"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
queue: Arc<Mutex<VecDeque<T>>>,
|
||||
user_events_sender: mpsc::Sender<T>,
|
||||
looper: ForeignLooper,
|
||||
}
|
||||
|
||||
impl<T> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
|
||||
self.queue.lock().unwrap().push_back(event);
|
||||
self.user_events_sender
|
||||
.send(event)
|
||||
.map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?;
|
||||
self.looper.wake();
|
||||
Ok(())
|
||||
}
|
||||
@@ -418,7 +625,7 @@ impl<T> EventLoopProxy<T> {
|
||||
impl<T> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EventLoopProxy {
|
||||
queue: self.queue.clone(),
|
||||
user_events_sender: self.user_events_sender.clone(),
|
||||
looper: self.looper.clone(),
|
||||
}
|
||||
}
|
||||
@@ -440,6 +647,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
v.push_back(MonitorHandle);
|
||||
v
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
@@ -451,6 +662,18 @@ impl WindowId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(_: WindowId) -> Self {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(_: u64) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct DeviceId;
|
||||
|
||||
@@ -466,7 +689,7 @@ pub struct PlatformSpecificWindowBuilderAttributes;
|
||||
pub struct Window;
|
||||
|
||||
impl Window {
|
||||
pub fn new<T: 'static>(
|
||||
pub(crate) fn new<T: 'static>(
|
||||
_el: &EventLoopWindowTarget<T>,
|
||||
_window_attrs: window::WindowAttributes,
|
||||
_: PlatformSpecificWindowBuilderAttributes,
|
||||
@@ -538,8 +761,16 @@ impl Window {
|
||||
|
||||
pub fn set_visible(&self, _visibility: bool) {}
|
||||
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_resizable(&self, _resizeable: bool) {}
|
||||
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_minimized(&self, _minimized: bool) {}
|
||||
|
||||
pub fn set_maximized(&self, _maximized: bool) {}
|
||||
@@ -558,12 +789,18 @@ impl Window {
|
||||
|
||||
pub fn set_decorations(&self, _decorations: bool) {}
|
||||
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {}
|
||||
|
||||
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
|
||||
|
||||
pub fn set_ime_position(&self, _position: Position) {}
|
||||
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {}
|
||||
|
||||
pub fn focus_window(&self) {}
|
||||
|
||||
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
|
||||
@@ -576,7 +813,7 @@ impl Window {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> {
|
||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
@@ -590,14 +827,22 @@ impl Window {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut handle = AndroidNdkHandle::empty();
|
||||
if let Some(native_window) = ndk_glue::native_window().as_ref() {
|
||||
handle.a_native_window = unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
|
||||
if let Some(native_window) = ndk_glue::native_window() {
|
||||
native_window.raw_window_handle()
|
||||
} else {
|
||||
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
|
||||
};
|
||||
RawWindowHandle::AndroidNdk(handle)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Configuration {
|
||||
@@ -651,20 +896,23 @@ impl MonitorHandle {
|
||||
.unwrap_or(1.0)
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
// FIXME no way to get real refresh rate for now.
|
||||
None
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = monitor::VideoMode> {
|
||||
let size = self.size().into();
|
||||
let mut v = Vec::new();
|
||||
// FIXME this is not the real refresh rate
|
||||
// (it is guarunteed to support 32 bit color though)
|
||||
v.push(monitor::VideoMode {
|
||||
// (it is guaranteed to support 32 bit color though)
|
||||
std::iter::once(monitor::VideoMode {
|
||||
video_mode: VideoMode {
|
||||
size,
|
||||
bit_depth: 32,
|
||||
refresh_rate: 60,
|
||||
refresh_rate_millihertz: 60000,
|
||||
monitor: self.clone(),
|
||||
},
|
||||
});
|
||||
v.into_iter()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,7 +920,7 @@ impl MonitorHandle {
|
||||
pub struct VideoMode {
|
||||
size: (u32, u32),
|
||||
bit_depth: u16,
|
||||
refresh_rate: u16,
|
||||
refresh_rate_millihertz: u32,
|
||||
monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
@@ -685,8 +933,8 @@ impl VideoMode {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> monitor::MonitorHandle {
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::{
|
||||
};
|
||||
|
||||
use objc::runtime::{BOOL, YES};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
@@ -52,11 +53,7 @@ enum UserCallbackTransitionResult<'a> {
|
||||
|
||||
impl Event<'static, Never> {
|
||||
fn is_redraw(&self) -> bool {
|
||||
if let Event::RedrawRequested(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(self, Event::RedrawRequested(_))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +116,7 @@ impl Drop for AppState {
|
||||
} => {
|
||||
for &mut window in queued_windows {
|
||||
unsafe {
|
||||
let () = msg_send![window, release];
|
||||
let _: () = msg_send![window, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,10 +197,10 @@ impl AppState {
|
||||
}
|
||||
|
||||
fn has_launched(&self) -> bool {
|
||||
match self.state() {
|
||||
&AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false,
|
||||
_ => true,
|
||||
}
|
||||
!matches!(
|
||||
self.state(),
|
||||
AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }
|
||||
)
|
||||
}
|
||||
|
||||
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
|
||||
@@ -296,7 +293,7 @@ impl AppState {
|
||||
};
|
||||
(waiting_event_handler, event)
|
||||
}
|
||||
(ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"),
|
||||
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
|
||||
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
|
||||
};
|
||||
|
||||
@@ -451,7 +448,7 @@ impl AppState {
|
||||
});
|
||||
self.waker.start()
|
||||
}
|
||||
(_, ControlFlow::Exit) => {
|
||||
(_, ControlFlow::ExitWithCode(_)) => {
|
||||
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
||||
// it is not possible to quit an iOS app gracefully and programatically
|
||||
warn!("`ControlFlow::Exit` ignored on iOS");
|
||||
@@ -528,7 +525,9 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
|
||||
| &mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_gpu_redraws,
|
||||
..
|
||||
} => drop(queued_gpu_redraws.insert(window)),
|
||||
} => {
|
||||
let _ = queued_gpu_redraws.insert(window);
|
||||
}
|
||||
s @ &mut AppStateImpl::ProcessingRedraws { .. }
|
||||
| s @ &mut AppStateImpl::Waiting { .. }
|
||||
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
|
||||
@@ -536,6 +535,7 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
|
||||
panic!("Attempt to create a `Window` after the app has terminated")
|
||||
}
|
||||
}
|
||||
|
||||
drop(this);
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
|
||||
pub unsafe fn did_finish_launching() {
|
||||
let mut this = AppState::get_mut();
|
||||
let windows = match this.state_mut() {
|
||||
AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()),
|
||||
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
|
||||
@@ -578,15 +578,15 @@ pub unsafe fn did_finish_launching() {
|
||||
// ```
|
||||
let screen: id = msg_send![window, screen];
|
||||
let _: id = msg_send![screen, retain];
|
||||
let () = msg_send![window, setScreen:0 as id];
|
||||
let () = msg_send![window, setScreen: screen];
|
||||
let () = msg_send![screen, release];
|
||||
let _: () = msg_send![window, setScreen:0 as id];
|
||||
let _: () = msg_send![window, setScreen: screen];
|
||||
let _: () = msg_send![screen, release];
|
||||
let controller: id = msg_send![window, rootViewController];
|
||||
let () = msg_send![window, setRootViewController:ptr::null::<()>()];
|
||||
let () = msg_send![window, setRootViewController: controller];
|
||||
let () = msg_send![window, makeKeyAndVisible];
|
||||
let _: () = msg_send![window, setRootViewController:ptr::null::<()>()];
|
||||
let _: () = msg_send![window, setRootViewController: controller];
|
||||
let _: () = msg_send![window, makeKeyAndVisible];
|
||||
}
|
||||
let () = msg_send![window, release];
|
||||
let _: () = msg_send![window, release];
|
||||
}
|
||||
|
||||
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
|
||||
@@ -603,9 +603,9 @@ pub unsafe fn did_finish_launching() {
|
||||
let count: NSUInteger = msg_send![window, retainCount];
|
||||
// make sure the window is still referenced
|
||||
if count > 1 {
|
||||
let () = msg_send![window, makeKeyAndVisible];
|
||||
let _: () = msg_send![window, makeKeyAndVisible];
|
||||
}
|
||||
let () = msg_send![window, release];
|
||||
let _: () = msg_send![window, release];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,7 +670,7 @@ pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events
|
||||
&mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_events,
|
||||
queued_gpu_redraws: _,
|
||||
} => mem::replace(queued_events, Vec::new()),
|
||||
} => mem::take(queued_events),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
if queued_events.is_empty() {
|
||||
@@ -751,7 +751,7 @@ unsafe fn handle_user_events() {
|
||||
&mut AppStateImpl::InUserCallback {
|
||||
ref mut queued_events,
|
||||
queued_gpu_redraws: _,
|
||||
} => mem::replace(queued_events, Vec::new()),
|
||||
} => mem::take(queued_events),
|
||||
s => bug!("unexpected state {:?}", s),
|
||||
};
|
||||
if queued_events.is_empty() {
|
||||
@@ -811,9 +811,7 @@ pub unsafe fn handle_main_events_cleared() {
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !redraw_events.is_empty() {
|
||||
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
|
||||
}
|
||||
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
|
||||
drop(this);
|
||||
|
||||
handle_nonuser_events(redraw_events);
|
||||
@@ -877,7 +875,7 @@ fn handle_hidpi_proxy(
|
||||
let size = CGSize::new(logical_size);
|
||||
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
|
||||
unsafe {
|
||||
let () = msg_send![view, setFrame: new_frame];
|
||||
let _: () = msg_send![view, setFrame: new_frame];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,20 +990,20 @@ macro_rules! os_capabilities {
|
||||
}
|
||||
|
||||
os_capabilities! {
|
||||
/// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
|
||||
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
|
||||
#[allow(unused)] // error message unused
|
||||
safe_area_err_msg: "-[UIView safeAreaInsets]",
|
||||
safe_area: 11-0,
|
||||
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc
|
||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
|
||||
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
|
||||
home_indicator_hidden: 11-0,
|
||||
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc
|
||||
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
|
||||
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
|
||||
defer_system_gestures: 11-0,
|
||||
/// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc
|
||||
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
|
||||
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
|
||||
maximum_frames_per_second: 10-3,
|
||||
/// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc
|
||||
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
|
||||
#[allow(unused)] // error message unused
|
||||
force_touch_err_msg: "-[UITouch force]",
|
||||
force_touch: 9-0,
|
||||
@@ -1018,29 +1016,27 @@ impl NSOperatingSystemVersion {
|
||||
}
|
||||
|
||||
pub fn os_capabilities() -> OSCapabilities {
|
||||
lazy_static! {
|
||||
static ref OS_CAPABILITIES: OSCapabilities = {
|
||||
let version: NSOperatingSystemVersion = unsafe {
|
||||
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
|
||||
let atleast_ios_8: BOOL = msg_send![
|
||||
process_info,
|
||||
respondsToSelector: sel!(operatingSystemVersion)
|
||||
];
|
||||
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
|
||||
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
|
||||
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
|
||||
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
|
||||
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(
|
||||
atleast_ios_8 == YES,
|
||||
"`winit` requires iOS version 8 or greater"
|
||||
);
|
||||
msg_send![process_info, operatingSystemVersion]
|
||||
};
|
||||
version.into()
|
||||
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
|
||||
let version: NSOperatingSystemVersion = unsafe {
|
||||
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
|
||||
let atleast_ios_8: BOOL = msg_send![
|
||||
process_info,
|
||||
respondsToSelector: sel!(operatingSystemVersion)
|
||||
];
|
||||
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
|
||||
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
|
||||
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
|
||||
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
|
||||
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(
|
||||
atleast_ios_8 == YES,
|
||||
"`winit` requires iOS version 8 or greater"
|
||||
);
|
||||
msg_send![process_info, operatingSystemVersion]
|
||||
};
|
||||
}
|
||||
version.into()
|
||||
});
|
||||
OS_CAPABILITIES.clone()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ use std::{
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
};
|
||||
|
||||
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::Event,
|
||||
@@ -63,14 +65,21 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
|
||||
Some(RootMonitorHandle { inner: monitor })
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
|
||||
static mut SINGLETON_INIT: bool = false;
|
||||
unsafe {
|
||||
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
|
||||
|
||||
@@ -88,7 +88,7 @@ pub enum UITouchPhase {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UIForceTouchCapability {
|
||||
@@ -97,7 +97,7 @@ pub enum UIForceTouchCapability {
|
||||
Available,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchType {
|
||||
@@ -144,10 +144,9 @@ impl From<Idiom> for UIUserInterfaceIdiom {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Idiom> for UIUserInterfaceIdiom {
|
||||
fn into(self) -> Idiom {
|
||||
match self {
|
||||
impl From<UIUserInterfaceIdiom> for Idiom {
|
||||
fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom {
|
||||
match ui_idiom {
|
||||
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
|
||||
UIUserInterfaceIdiom::Phone => Idiom::Phone,
|
||||
UIUserInterfaceIdiom::Pad => Idiom::Pad,
|
||||
@@ -230,9 +229,9 @@ impl From<ScreenEdge> for UIRectEdge {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ScreenEdge> for UIRectEdge {
|
||||
fn into(self) -> ScreenEdge {
|
||||
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
|
||||
impl From<UIRectEdge> for ScreenEdge {
|
||||
fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge {
|
||||
let bits: u8 = ui_rect_edge.0.try_into().expect("invalid `UIRectEdge`");
|
||||
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +56,15 @@
|
||||
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
|
||||
|
||||
#![cfg(target_os = "ios")]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
|
||||
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
|
||||
// window size/position.
|
||||
macro_rules! assert_main_thread {
|
||||
($($t:tt)*) => {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
let is_main_thread: ::objc::runtime::BOOL = msg_send!(class!(NSThread), isMainThread);
|
||||
if is_main_thread == ::objc::runtime::NO {
|
||||
panic!($($t)*);
|
||||
}
|
||||
};
|
||||
@@ -77,8 +79,10 @@ mod window;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
pub(crate) use self::{
|
||||
event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
@@ -105,9 +109,7 @@ unsafe impl Sync for DeviceId {}
|
||||
pub enum OsError {}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "os error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
pub(crate) refresh_rate_millihertz: u32,
|
||||
pub(crate) screen_mode: NativeDisplayMode,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
@@ -30,7 +30,7 @@ unsafe impl Send for NativeDisplayMode {}
|
||||
impl Drop for NativeDisplayMode {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.0, release];
|
||||
let _: () = msg_send![self.0, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ impl Clone for VideoMode {
|
||||
VideoMode {
|
||||
size: self.size,
|
||||
bit_depth: self.bit_depth,
|
||||
refresh_rate: self.refresh_rate,
|
||||
refresh_rate_millihertz: self.refresh_rate_millihertz,
|
||||
screen_mode: self.screen_mode.clone(),
|
||||
monitor: self.monitor.clone(),
|
||||
}
|
||||
@@ -59,30 +59,14 @@ impl Clone for VideoMode {
|
||||
impl VideoMode {
|
||||
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
|
||||
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
|
||||
msg_send![uiscreen, maximumFramesPerSecond]
|
||||
} else {
|
||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
||||
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
||||
//
|
||||
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
|
||||
// supported, they are all guaranteed to have 60hz refresh rates. This does not
|
||||
// correctly handle external displays. ProMotion displays support 120fps, but they were
|
||||
// introduced at the same time as the `maximumFramesPerSecond` API.
|
||||
//
|
||||
// FIXME: earlier OSs could calculate the refresh rate using
|
||||
// `-[CADisplayLink duration]`.
|
||||
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
||||
60
|
||||
};
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
|
||||
let size: CGSize = msg_send![screen_mode, size];
|
||||
let screen_mode: id = msg_send![screen_mode, retain];
|
||||
let screen_mode = NativeDisplayMode(screen_mode);
|
||||
VideoMode {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
bit_depth: 32,
|
||||
refresh_rate: refresh_rate as u16,
|
||||
refresh_rate_millihertz,
|
||||
screen_mode,
|
||||
monitor: MonitorHandle::retained_new(uiscreen),
|
||||
}
|
||||
@@ -96,8 +80,8 @@ impl VideoMode {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
@@ -115,7 +99,7 @@ pub struct Inner {
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.uiscreen, release];
|
||||
let _: () = msg_send![self.uiscreen, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +152,9 @@ impl Drop for MonitorHandle {
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// TODO: Do this using the proper fmt API
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct MonitorHandle {
|
||||
name: Option<String>,
|
||||
size: PhysicalSize<u32>,
|
||||
@@ -191,7 +177,7 @@ impl MonitorHandle {
|
||||
pub fn retained_new(uiscreen: id) -> MonitorHandle {
|
||||
unsafe {
|
||||
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
|
||||
let () = msg_send![uiscreen, retain];
|
||||
let _: () = msg_send![uiscreen, retain];
|
||||
}
|
||||
MonitorHandle {
|
||||
inner: Inner { uiscreen },
|
||||
@@ -237,6 +223,10 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
Some(refresh_rate_millihertz(self.uiscreen))
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
let mut modes = BTreeSet::new();
|
||||
unsafe {
|
||||
@@ -255,6 +245,30 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
|
||||
let refresh_rate_millihertz: NSInteger = unsafe {
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.maximum_frames_per_second {
|
||||
msg_send![uiscreen, maximumFramesPerSecond]
|
||||
} else {
|
||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
||||
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
||||
//
|
||||
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
|
||||
// supported, they are all guaranteed to have 60hz refresh rates. This does not
|
||||
// correctly handle external displays. ProMotion displays support 120fps, but they were
|
||||
// introduced at the same time as the `maximumFramesPerSecond` API.
|
||||
//
|
||||
// FIXME: earlier OSs could calculate the refresh rate using
|
||||
// `-[CADisplayLink duration]`.
|
||||
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
||||
60
|
||||
}
|
||||
};
|
||||
|
||||
refresh_rate_millihertz as u32 * 1000
|
||||
}
|
||||
|
||||
// MonitorHandleExtIOS
|
||||
impl Inner {
|
||||
pub fn ui_screen(&self) -> id {
|
||||
|
||||
@@ -112,14 +112,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
))),
|
||||
);
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![super(object, superclass), drawRect: rect];
|
||||
let _: () = msg_send![super(object, superclass), drawRect: rect];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn layout_subviews(object: &Object, _: Sel) {
|
||||
unsafe {
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![super(object, superclass), layoutSubviews];
|
||||
let _: () = msg_send![super(object, superclass), layoutSubviews];
|
||||
|
||||
let window: id = msg_send![object, window];
|
||||
assert!(!window.is_null());
|
||||
@@ -133,14 +133,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
}
|
||||
.to_physical(scale_factor.into());
|
||||
.to_physical(scale_factor as f64);
|
||||
|
||||
// If the app is started in landscape, the view frame and window bounds can be mismatched.
|
||||
// The view frame will be in portrait and the window bounds in landscape. So apply the
|
||||
// window bounds to the view frame to make it consistent.
|
||||
let view_frame: CGRect = msg_send![object, frame];
|
||||
if view_frame != window_bounds {
|
||||
let () = msg_send![object, setFrame: window_bounds];
|
||||
let _: () = msg_send![object, setFrame: window_bounds];
|
||||
}
|
||||
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
@@ -157,7 +157,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
) {
|
||||
unsafe {
|
||||
let superclass: &'static Class = msg_send![object, superclass];
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
super(object, superclass),
|
||||
setContentScaleFactor: untrusted_scale_factor
|
||||
];
|
||||
@@ -180,7 +180,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
&& scale_factor > 0.0,
|
||||
"invalid scale_factor set on UIView",
|
||||
);
|
||||
let scale_factor: f64 = scale_factor.into();
|
||||
let scale_factor = scale_factor as f64;
|
||||
let bounds: CGRect = msg_send![object, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
@@ -340,7 +340,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
|
||||
prefers_status_bar_hidden: BOOL,
|
||||
setPrefersStatusBarHidden: |object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
||||
let _: () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
},
|
||||
prefersStatusBarHidden,
|
||||
@@ -353,7 +353,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
|
||||
OSCapabilities::home_indicator_hidden_err_msg;
|
||||
|object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
let _: () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
}
|
||||
},
|
||||
prefersHomeIndicatorAutoHidden,
|
||||
@@ -363,7 +363,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
|
||||
supported_orientations: UIInterfaceOrientationMask,
|
||||
setSupportedInterfaceOrientations: |object| {
|
||||
unsafe {
|
||||
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
||||
let _: () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
||||
}
|
||||
},
|
||||
supportedInterfaceOrientations,
|
||||
@@ -376,7 +376,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
|
||||
OSCapabilities::defer_system_gestures_err_msg;
|
||||
|object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
let _: () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}
|
||||
},
|
||||
preferredScreenEdgesDeferringSystemGestures,
|
||||
@@ -398,7 +398,7 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Focused(true),
|
||||
}));
|
||||
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
|
||||
let _: () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
window_id: RootWindowId(object.into()),
|
||||
event: WindowEvent::Focused(false),
|
||||
}));
|
||||
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
||||
let _: () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +429,7 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view(
|
||||
pub(crate) unsafe fn create_view(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
@@ -440,16 +440,16 @@ pub unsafe fn create_view(
|
||||
assert!(!view.is_null(), "Failed to create `UIView` instance");
|
||||
let view: id = msg_send![view, initWithFrame: frame];
|
||||
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
|
||||
let () = msg_send![view, setMultipleTouchEnabled: YES];
|
||||
let _: () = msg_send![view, setMultipleTouchEnabled: YES];
|
||||
if let Some(scale_factor) = platform_attributes.scale_factor {
|
||||
let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
|
||||
let _: () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
|
||||
}
|
||||
|
||||
view
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view_controller(
|
||||
pub(crate) unsafe fn create_view_controller(
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: id,
|
||||
@@ -484,28 +484,28 @@ pub unsafe fn create_view_controller(
|
||||
let edges: UIRectEdge = platform_attributes
|
||||
.preferred_screen_edges_deferring_system_gestures
|
||||
.into();
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
view_controller,
|
||||
setSupportedInterfaceOrientations: supported_orientations
|
||||
];
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
view_controller,
|
||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
||||
];
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
view_controller,
|
||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||
];
|
||||
let () = msg_send![view_controller, setView: view];
|
||||
let _: () = msg_send![view_controller, setView: view];
|
||||
view_controller
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_window(
|
||||
pub(crate) unsafe fn create_window(
|
||||
window_attributes: &WindowAttributes,
|
||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
@@ -520,11 +520,11 @@ pub unsafe fn create_window(
|
||||
!window.is_null(),
|
||||
"Failed to initialize `UIWindow` instance"
|
||||
);
|
||||
let () = msg_send![window, setRootViewController: view_controller];
|
||||
let _: () = msg_send![window, setRootViewController: view_controller];
|
||||
match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let uiscreen = video_mode.monitor().ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
|
||||
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
|
||||
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
|
||||
}
|
||||
Some(Fullscreen::Borderless(ref monitor)) => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use raw_window_handle::{RawWindowHandle, UiKitHandle};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use objc::runtime::{Class, Object, BOOL, NO, YES};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
|
||||
|
||||
use crate::{
|
||||
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||
@@ -23,7 +23,8 @@ use crate::{
|
||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
||||
},
|
||||
window::{
|
||||
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
|
||||
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
|
||||
WindowId as RootWindowId,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -37,9 +38,9 @@ pub struct Inner {
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let () = msg_send![self.view, release];
|
||||
let () = msg_send![self.view_controller, release];
|
||||
let () = msg_send![self.window, release];
|
||||
let _: () = msg_send![self.view, release];
|
||||
let _: () = msg_send![self.view_controller, release];
|
||||
let _: () = msg_send![self.window, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,14 +53,19 @@ impl Inner {
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match visible {
|
||||
true => unsafe {
|
||||
let () = msg_send![self.window, setHidden: NO];
|
||||
let _: () = msg_send![self.window, setHidden: NO];
|
||||
},
|
||||
false => unsafe {
|
||||
let () = msg_send![self.window, setHidden: YES];
|
||||
let _: () = msg_send![self.window, setHidden: YES];
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
warn!("`Window::is_visible` is ignored on iOS");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
unsafe {
|
||||
if self.gl_or_metal_backed {
|
||||
@@ -73,7 +79,7 @@ impl Inner {
|
||||
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
|
||||
app_state::queue_gl_or_metal_redraw(self.window);
|
||||
} else {
|
||||
let () = msg_send![self.view, setNeedsDisplay];
|
||||
let _: () = msg_send![self.view, setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,8 +120,8 @@ impl Inner {
|
||||
},
|
||||
size: screen_frame.size,
|
||||
};
|
||||
let bounds = self.from_screen_space(new_screen_frame);
|
||||
let () = msg_send![self.window, setBounds: bounds];
|
||||
let bounds = self.rect_from_screen_space(new_screen_frame);
|
||||
let _: () = msg_send![self.window, setBounds: bounds];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +165,11 @@ impl Inner {
|
||||
warn!("`Window::set_resizable` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
warn!("`Window::is_resizable` is ignored on iOS");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
unsafe {
|
||||
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
|
||||
@@ -174,7 +185,7 @@ impl Inner {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
@@ -186,6 +197,10 @@ impl Inner {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_minimized(&self, _minimized: bool) {
|
||||
warn!("`Window::set_minimized` is ignored on iOS")
|
||||
}
|
||||
@@ -204,7 +219,8 @@ impl Inner {
|
||||
let uiscreen = match monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||
let _: () =
|
||||
msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
|
||||
uiscreen
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => monitor
|
||||
@@ -219,16 +235,16 @@ impl Inner {
|
||||
// this is pretty slow on iOS, so avoid doing it if we can
|
||||
let current: id = msg_send![self.window, screen];
|
||||
if uiscreen != current {
|
||||
let () = msg_send![self.window, setScreen: uiscreen];
|
||||
let _: () = msg_send![self.window, setScreen: uiscreen];
|
||||
}
|
||||
|
||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
let () = msg_send![self.window, setFrame: bounds];
|
||||
let _: () = msg_send![self.window, setFrame: bounds];
|
||||
|
||||
// For external displays, we must disable overscan compensation or
|
||||
// the displayed image will have giant black bars surrounding it on
|
||||
// each side
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
uiscreen,
|
||||
setOverscanCompensation: UIScreenOverscanCompensation::None
|
||||
];
|
||||
@@ -259,6 +275,11 @@ impl Inner {
|
||||
warn!("`Window::set_decorations` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
warn!("`Window::is_decorated` is ignored on iOS");
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
warn!("`Window::set_always_on_top` is ignored on iOS")
|
||||
}
|
||||
@@ -271,6 +292,10 @@ impl Inner {
|
||||
warn!("`Window::set_ime_position` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {
|
||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn focus_window(&self) {
|
||||
warn!("`Window::set_focus` is ignored on iOS")
|
||||
}
|
||||
@@ -307,11 +332,15 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut handle = UiKitHandle::empty();
|
||||
handle.ui_window = self.window as _;
|
||||
handle.ui_view = self.view as _;
|
||||
handle.ui_view_controller = self.view_controller as _;
|
||||
RawWindowHandle::UiKit(handle)
|
||||
let mut window_handle = UiKitWindowHandle::empty();
|
||||
window_handle.ui_window = self.window as _;
|
||||
window_handle.ui_view = self.view as _;
|
||||
window_handle.ui_view_controller = self.view_controller as _;
|
||||
RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,15 +380,15 @@ impl DerefMut for Window {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
pub(crate) fn new<T>(
|
||||
_event_loop: &EventLoopWindowTarget<T>,
|
||||
window_attributes: WindowAttributes,
|
||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
if let Some(_) = window_attributes.min_inner_size {
|
||||
if window_attributes.min_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
||||
}
|
||||
if let Some(_) = window_attributes.max_inner_size {
|
||||
if window_attributes.max_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
|
||||
}
|
||||
if window_attributes.always_on_top {
|
||||
@@ -395,7 +424,7 @@ impl Window {
|
||||
None => screen_bounds,
|
||||
};
|
||||
|
||||
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
|
||||
let view = view::create_view(&window_attributes, &platform_attributes, frame);
|
||||
|
||||
let gl_or_metal_backed = {
|
||||
let view_class: id = msg_send![view, class];
|
||||
@@ -428,7 +457,7 @@ impl Window {
|
||||
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
|
||||
// event on window creation if the DPI factor != 1.0
|
||||
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
|
||||
let scale_factor: f64 = scale_factor.into();
|
||||
let scale_factor = scale_factor as f64;
|
||||
if scale_factor != 1.0 {
|
||||
let bounds: CGRect = msg_send![view, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
@@ -478,7 +507,7 @@ impl Inner {
|
||||
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
|
||||
);
|
||||
let scale_factor = scale_factor as CGFloat;
|
||||
let () = msg_send![self.view, setContentScaleFactor: scale_factor];
|
||||
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,7 +528,7 @@ impl Inner {
|
||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
||||
];
|
||||
@@ -509,7 +538,7 @@ impl Inner {
|
||||
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
let edges: UIRectEdge = edges.into();
|
||||
unsafe {
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
self.view_controller,
|
||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||
];
|
||||
@@ -519,7 +548,7 @@ impl Inner {
|
||||
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let status_bar_hidden = if hidden { YES } else { NO };
|
||||
let () = msg_send![
|
||||
let _: () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
@@ -530,11 +559,11 @@ impl Inner {
|
||||
impl Inner {
|
||||
// requires main thread
|
||||
unsafe fn screen_frame(&self) -> CGRect {
|
||||
self.to_screen_space(msg_send![self.window, bounds])
|
||||
self.rect_to_screen_space(msg_send![self.window, bounds])
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
@@ -545,7 +574,7 @@ impl Inner {
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
@@ -570,9 +599,9 @@ impl Inner {
|
||||
height: bounds.size.height - safe_area.top - safe_area.bottom,
|
||||
},
|
||||
};
|
||||
self.to_screen_space(safe_bounds)
|
||||
self.rect_to_screen_space(safe_bounds)
|
||||
} else {
|
||||
let screen_frame = self.to_screen_space(bounds);
|
||||
let screen_frame = self.rect_to_screen_space(bounds);
|
||||
let status_bar_frame: CGRect = {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert!(
|
||||
@@ -616,6 +645,20 @@ impl WindowId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(window_id: WindowId) -> Self {
|
||||
window_id.window as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(raw_id: u64) -> Self {
|
||||
Self {
|
||||
window: raw_id as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowId {}
|
||||
unsafe impl Sync for WindowId {}
|
||||
|
||||
|
||||
@@ -11,26 +11,35 @@ compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
use std::error::Error;
|
||||
|
||||
use std::{collections::VecDeque, env, fmt};
|
||||
#[cfg(feature = "x11")]
|
||||
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
use once_cell::sync::Lazy;
|
||||
#[cfg(feature = "x11")]
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub use self::x11::XNotSupported;
|
||||
#[cfg(feature = "x11")]
|
||||
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
|
||||
#[cfg(feature = "x11")]
|
||||
use crate::platform::unix::XlibErrorHook;
|
||||
#[cfg(feature = "wayland")]
|
||||
use crate::window::Theme;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
event_loop::{
|
||||
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
||||
},
|
||||
icon::Icon,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
|
||||
};
|
||||
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
@@ -49,8 +58,35 @@ pub mod x11;
|
||||
/// If this variable is set with any other value, winit will panic.
|
||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum Backend {
|
||||
#[cfg(feature = "x11")]
|
||||
X,
|
||||
#[cfg(feature = "wayland")]
|
||||
Wayland,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
||||
pub(crate) forced_backend: Option<Backend>,
|
||||
pub(crate) any_thread: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ApplicationName {
|
||||
pub general: String,
|
||||
pub instance: String,
|
||||
}
|
||||
|
||||
impl ApplicationName {
|
||||
pub fn new(general: String, instance: String) -> Self {
|
||||
Self { general, instance }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub name: Option<ApplicationName>,
|
||||
#[cfg(feature = "x11")]
|
||||
pub visual_infos: Option<XVisualInfo>,
|
||||
#[cfg(feature = "x11")]
|
||||
@@ -60,20 +96,19 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
#[cfg(feature = "x11")]
|
||||
pub base_size: Option<Size>,
|
||||
#[cfg(feature = "x11")]
|
||||
pub class: Option<(String, String)>,
|
||||
#[cfg(feature = "x11")]
|
||||
pub override_redirect: bool,
|
||||
#[cfg(feature = "x11")]
|
||||
pub x11_window_types: Vec<XWindowType>,
|
||||
#[cfg(feature = "x11")]
|
||||
pub gtk_theme_variant: Option<String>,
|
||||
#[cfg(feature = "wayland")]
|
||||
pub app_id: Option<String>,
|
||||
pub csd_theme: Option<Theme>,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
#[cfg(feature = "x11")]
|
||||
visual_infos: None,
|
||||
#[cfg(feature = "x11")]
|
||||
@@ -83,24 +118,20 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
#[cfg(feature = "x11")]
|
||||
base_size: None,
|
||||
#[cfg(feature = "x11")]
|
||||
class: None,
|
||||
#[cfg(feature = "x11")]
|
||||
override_redirect: false,
|
||||
#[cfg(feature = "x11")]
|
||||
x11_window_types: vec![XWindowType::Normal],
|
||||
#[cfg(feature = "x11")]
|
||||
gtk_theme_variant: None,
|
||||
#[cfg(feature = "wayland")]
|
||||
app_id: None,
|
||||
csd_theme: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
lazy_static! {
|
||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
|
||||
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new));
|
||||
}
|
||||
pub static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
|
||||
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OsError {
|
||||
@@ -118,9 +149,9 @@ impl fmt::Display for OsError {
|
||||
#[cfg(feature = "x11")]
|
||||
OsError::XError(ref e) => _f.pad(&e.description),
|
||||
#[cfg(feature = "x11")]
|
||||
OsError::XMisc(ref e) => _f.pad(e),
|
||||
OsError::XMisc(e) => _f.pad(e),
|
||||
#[cfg(feature = "wayland")]
|
||||
OsError::WaylandMisc(ref e) => _f.pad(e),
|
||||
OsError::WaylandMisc(e) => _f.pad(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,19 +164,23 @@ pub enum Window {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum WindowId {
|
||||
#[cfg(feature = "x11")]
|
||||
X(x11::WindowId),
|
||||
#[cfg(feature = "wayland")]
|
||||
Wayland(wayland::WindowId),
|
||||
pub struct WindowId(u64);
|
||||
|
||||
impl From<WindowId> for u64 {
|
||||
fn from(window_id: WindowId) -> Self {
|
||||
window_id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for WindowId {
|
||||
fn from(raw_id: u64) -> Self {
|
||||
Self(raw_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
#[cfg(feature = "wayland")]
|
||||
return WindowId::Wayland(wayland::WindowId::dummy());
|
||||
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
|
||||
return WindowId::X(x11::WindowId::dummy());
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +258,11 @@ impl MonitorHandle {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.position())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64)
|
||||
@@ -254,8 +294,8 @@ impl VideoMode {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate())
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -266,7 +306,7 @@ impl VideoMode {
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn new<T>(
|
||||
pub(crate) fn new<T>(
|
||||
window_target: &EventLoopWindowTarget<T>,
|
||||
attribs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
@@ -285,7 +325,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
x11_or_wayland!(match self; Window(w) => w.id(); as WindowId)
|
||||
match self {
|
||||
#[cfg(feature = "wayland")]
|
||||
Self::Wayland(window) => window.id(),
|
||||
#[cfg(feature = "x11")]
|
||||
Self::X(window) => window.id(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -298,6 +343,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_visible())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.outer_position())
|
||||
@@ -343,14 +393,19 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_resizable())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab))
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -363,6 +418,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(window) => window.drag_window())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
|
||||
@@ -403,13 +463,18 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
x11_or_wayland!(match self; Window(w) => w.is_decorated())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
match self {
|
||||
#[cfg(feature = "x11")]
|
||||
Window::X(ref w) => w.set_always_on_top(_always_on_top),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => (),
|
||||
Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,7 +484,7 @@ impl Window {
|
||||
#[cfg(feature = "x11")]
|
||||
Window::X(ref w) => w.set_window_icon(_window_icon),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => (),
|
||||
Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,13 +493,18 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
match self {
|
||||
#[cfg(feature = "x11")]
|
||||
Window::X(ref w) => w.focus_window(),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => (),
|
||||
Window::Wayland(_) => (),
|
||||
}
|
||||
}
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
@@ -504,16 +574,21 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
match self {
|
||||
#[cfg(feature = "x11")]
|
||||
Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
|
||||
#[cfg(feature = "wayland")]
|
||||
Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
|
||||
}
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_window_handle())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
|
||||
}
|
||||
}
|
||||
|
||||
/// Hooks for X11 errors.
|
||||
#[cfg(feature = "x11")]
|
||||
pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
unsafe extern "C" fn x_error_callback(
|
||||
display: *mut x11::ffi::Display,
|
||||
@@ -521,6 +596,12 @@ unsafe extern "C" fn x_error_callback(
|
||||
) -> c_int {
|
||||
let xconn_lock = X11_BACKEND.lock();
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
// Call all the hooks.
|
||||
let mut error_handled = false;
|
||||
for hook in XLIB_ERROR_HOOKS.lock().iter() {
|
||||
error_handled |= hook(display as *mut _, event as *mut _);
|
||||
}
|
||||
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
|
||||
@@ -539,7 +620,10 @@ unsafe extern "C" fn x_error_callback(
|
||||
minor_code: (*event).minor_code,
|
||||
};
|
||||
|
||||
error!("X11 error: {:#?}", error);
|
||||
// Don't log error.
|
||||
if !error_handled {
|
||||
error!("X11 error: {:#?}", error);
|
||||
}
|
||||
|
||||
*xconn.latest_error.lock() = Some(error);
|
||||
}
|
||||
@@ -568,13 +652,28 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
assert_is_main_thread("new_any_thread");
|
||||
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
|
||||
if !attributes.any_thread && !is_main_thread() {
|
||||
panic!(
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
cross-platform compatibility hazard. If you absolutely need to create an \
|
||||
EventLoop on a different thread, you can use the \
|
||||
`EventLoopBuilderExtUnix::any_thread` function."
|
||||
);
|
||||
}
|
||||
|
||||
EventLoop::new_any_thread()
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
if attributes.forced_backend == Some(Backend::X) {
|
||||
// TODO: Propagate
|
||||
return EventLoop::new_x11_any_thread().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
if attributes.forced_backend == Some(Backend::Wayland) {
|
||||
// TODO: Propagate
|
||||
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
|
||||
}
|
||||
|
||||
pub fn new_any_thread() -> EventLoop<T> {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
@@ -623,26 +722,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn new_wayland() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
assert_is_main_thread("new_wayland_any_thread");
|
||||
|
||||
EventLoop::new_wayland_any_thread()
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
wayland::EventLoop::new().map(EventLoop::Wayland)
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
|
||||
assert_is_main_thread("new_x11_any_thread");
|
||||
|
||||
EventLoop::new_x11_any_thread()
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
let xconn = match X11_BACKEND.lock().as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(err) => return Err(err.clone()),
|
||||
@@ -655,7 +740,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, callback: F)
|
||||
pub fn run_return<F>(&mut self, callback: F) -> i32
|
||||
where
|
||||
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
@@ -670,7 +755,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
||||
x11_or_wayland!(match self; EventLoop(evl) => evl.window_target())
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,6 +816,20 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
|
||||
match *self {
|
||||
#[cfg(feature = "wayland")]
|
||||
EventLoopWindowTarget::Wayland(_) => (),
|
||||
#[cfg(feature = "x11")]
|
||||
EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle())
|
||||
}
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
@@ -741,26 +840,12 @@ fn sticky_exit_callback<T, F>(
|
||||
) where
|
||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
// make ControlFlow::Exit sticky by providing a dummy
|
||||
// control flow reference if it is already Exit.
|
||||
let mut dummy = ControlFlow::Exit;
|
||||
let cf = if *control_flow == ControlFlow::Exit {
|
||||
&mut dummy
|
||||
// make ControlFlow::ExitWithCode sticky by providing a dummy
|
||||
// control flow reference if it is already ExitWithCode.
|
||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
||||
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
|
||||
} else {
|
||||
control_flow
|
||||
};
|
||||
// user callback
|
||||
callback(evt, target, cf)
|
||||
}
|
||||
|
||||
fn assert_is_main_thread(suggested_method: &str) {
|
||||
if !is_main_thread() {
|
||||
panic!(
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
cross-platform compatibility hazard. If you really, absolutely need to create an \
|
||||
EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.",
|
||||
suggested_method
|
||||
);
|
||||
callback(evt, target, control_flow)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,23 +24,23 @@ use sctk::shm::ShmHandler;
|
||||
/// Set of extra features that are supported by the compositor.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WindowingFeatures {
|
||||
cursor_grab: bool,
|
||||
pointer_constraints: bool,
|
||||
xdg_activation: bool,
|
||||
}
|
||||
|
||||
impl WindowingFeatures {
|
||||
/// Create `WindowingFeatures` based on the presented interfaces.
|
||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
||||
let cursor_grab = env.get_global::<ZwpPointerConstraintsV1>().is_some();
|
||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
|
||||
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
|
||||
Self {
|
||||
cursor_grab,
|
||||
pointer_constraints,
|
||||
xdg_activation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_grab(&self) -> bool {
|
||||
self.cursor_grab
|
||||
pub fn pointer_constraints(&self) -> bool {
|
||||
self.pointer_constraints
|
||||
}
|
||||
|
||||
pub fn xdg_activation(&self) -> bool {
|
||||
|
||||
@@ -6,6 +6,8 @@ use std::process;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::client::Display;
|
||||
@@ -32,10 +34,9 @@ mod sink;
|
||||
mod state;
|
||||
|
||||
pub use proxy::EventLoopProxy;
|
||||
pub use sink::EventSink;
|
||||
pub use state::WinitState;
|
||||
|
||||
use sink::EventSink;
|
||||
|
||||
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
@@ -65,13 +66,21 @@ pub struct EventLoopWindowTarget<T> {
|
||||
|
||||
/// Theme manager to manage cursors.
|
||||
///
|
||||
/// It's being shared amoung all windows to avoid loading
|
||||
/// It's being shared between all windows to avoid loading
|
||||
/// multiple similar themes.
|
||||
pub theme_manager: ThemeManager,
|
||||
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
display_handle.display = self.display.get_display_ptr() as *mut _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
/// Event loop.
|
||||
event_loop: calloop::EventLoop<'static, WinitState>,
|
||||
@@ -206,19 +215,15 @@ impl<T: 'static> EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
|
||||
{
|
||||
self.run_return(callback);
|
||||
process::exit(0)
|
||||
let exit_code = self.run_return(callback);
|
||||
process::exit(exit_code);
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, mut callback: F)
|
||||
pub fn run_return<F>(&mut self, mut callback: F) -> i32
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
// Send pending events to the server.
|
||||
let _ = self.display.flush();
|
||||
|
||||
let mut control_flow = ControlFlow::default();
|
||||
|
||||
let mut control_flow = ControlFlow::Poll;
|
||||
let pending_user_events = self.pending_user_events.clone();
|
||||
|
||||
callback(
|
||||
@@ -227,6 +232,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut control_flow,
|
||||
);
|
||||
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
callback(Event::Resumed, &self.window_target, &mut control_flow);
|
||||
|
||||
let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
|
||||
let mut event_sink_back_buffer = Vec::new();
|
||||
|
||||
@@ -235,7 +244,108 @@ impl<T: 'static> EventLoop<T> {
|
||||
// really an option. Instead we inform that the event loop got destroyed. We may
|
||||
// communicate an error that something was terminated, but winit doesn't provide us
|
||||
// with an API to do that via some event.
|
||||
loop {
|
||||
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
|
||||
let exit_code = loop {
|
||||
// Send pending events to the server.
|
||||
let _ = self.display.flush();
|
||||
|
||||
// During the run of the user callback, some other code monitoring and reading the
|
||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
||||
// is the case, some events may have been enqueued in our event queue.
|
||||
//
|
||||
// If some messages are there, the event loop needs to behave as if it was instantly
|
||||
// woken up by messages arriving from the Wayland socket, to avoid delaying the
|
||||
// dispatch of these events until we're woken up again.
|
||||
let instant_wakeup = {
|
||||
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
|
||||
let queue = wayland_source.queue();
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => {
|
||||
window_target.state.get_mut()
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
|
||||
Ok(dispatched) => dispatched > 0,
|
||||
Err(error) => break error.raw_os_error().unwrap_or(1),
|
||||
}
|
||||
};
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::ExitWithCode(code) => break code,
|
||||
ControlFlow::Poll => {
|
||||
// Non-blocking dispatch.
|
||||
let timeout = Duration::from_millis(0);
|
||||
if let Err(error) = self.loop_dispatch(Some(timeout)) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Poll),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
let timeout = if instant_wakeup {
|
||||
Some(Duration::from_millis(0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Err(error) = self.loop_dispatch(timeout) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
let start = Instant::now();
|
||||
|
||||
// Compute the amount of time we'll block for.
|
||||
let duration = if deadline > start && !instant_wakeup {
|
||||
deadline - start
|
||||
} else {
|
||||
Duration::from_millis(0)
|
||||
};
|
||||
|
||||
if let Err(error) = self.loop_dispatch(Some(duration)) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
if now < deadline {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(deadline),
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
} else {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle pending user events. We don't need back buffer, since we can't dispatch
|
||||
// user events indirectly via callback to the user.
|
||||
for user_event in pending_user_events.borrow_mut().drain(..) {
|
||||
@@ -273,9 +383,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
),
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut physical_size,
|
||||
@@ -325,9 +433,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
if let Some(physical_size) = physical_size {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
),
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
@@ -340,9 +446,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
if window_update.close_window {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
),
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::CloseRequested,
|
||||
},
|
||||
&self.window_target,
|
||||
@@ -392,9 +496,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Handle redraw request.
|
||||
if window_update.redraw_requested {
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
)),
|
||||
Event::RedrawRequested(crate::window::WindowId(*window_id)),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
@@ -409,110 +511,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// Send pending events to the server.
|
||||
let _ = self.display.flush();
|
||||
|
||||
// During the run of the user callback, some other code monitoring and reading the
|
||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
||||
// is the case, some events may have been enqueued in our event queue.
|
||||
//
|
||||
// If some messages are there, the event loop needs to behave as if it was instantly
|
||||
// woken up by messages arriving from the Wayland socket, to avoid delaying the
|
||||
// dispatch of these events until we're woken up again.
|
||||
let instant_wakeup = {
|
||||
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
|
||||
let queue = wayland_source.queue();
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => {
|
||||
window_target.state.get_mut()
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Ok(dispatched) = queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
|
||||
dispatched > 0
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break,
|
||||
ControlFlow::Poll => {
|
||||
// Non-blocking dispatch.
|
||||
let timeout = Duration::from_millis(0);
|
||||
if self.loop_dispatch(Some(timeout)).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Poll),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
let timeout = if instant_wakeup {
|
||||
Some(Duration::from_millis(0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.loop_dispatch(timeout).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start: Instant::now(),
|
||||
requested_resume: None,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
let start = Instant::now();
|
||||
|
||||
// Compute the amount of time we'll block for.
|
||||
let duration = if deadline > start && !instant_wakeup {
|
||||
deadline - start
|
||||
} else {
|
||||
Duration::from_millis(0)
|
||||
};
|
||||
|
||||
if self.loop_dispatch(Some(duration)).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
if now < deadline {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(deadline),
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
} else {
|
||||
callback(
|
||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
}),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
|
||||
exit_code
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -536,12 +538,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
|
||||
let mut state = match &mut self.window_target.p {
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.event_loop.dispatch(timeout, &mut state)
|
||||
self.event_loop
|
||||
.dispatch(timeout, state)
|
||||
.map_err(|error| error.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! An event loop's sink to deliver events from the Wayland event callbacks.
|
||||
|
||||
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
|
||||
use crate::platform_impl::platform::{DeviceId as PlatformDeviceId, WindowId as PlatformWindowId};
|
||||
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
|
||||
use super::{DeviceId, WindowId};
|
||||
@@ -30,7 +30,7 @@ impl EventSink {
|
||||
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
|
||||
self.window_events.push(Event::WindowEvent {
|
||||
event,
|
||||
window_id: RootWindowId(PlatformWindowId::Wayland(window_id)),
|
||||
window_id: RootWindowId(window_id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
|
||||
pub use crate::platform_impl::platform::WindowId;
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
pub use window::Window;
|
||||
@@ -27,16 +28,7 @@ impl DeviceId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(usize);
|
||||
|
||||
impl WindowId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.as_ref().c_ptr() as usize)
|
||||
WindowId(surface.as_ref().c_ptr() as u64)
|
||||
}
|
||||
|
||||
@@ -167,6 +167,16 @@ impl MonitorHandle {
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> i32 {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
|
||||
@@ -175,14 +185,14 @@ impl MonitorHandle {
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
|
||||
.unwrap_or_else(Vec::new);
|
||||
.unwrap_or_default();
|
||||
|
||||
let monitor = self.clone();
|
||||
|
||||
modes.into_iter().map(move |mode| RootVideoMode {
|
||||
video_mode: PlatformVideoMode::Wayland(VideoMode {
|
||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
||||
refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16,
|
||||
refresh_rate_millihertz: mode.refresh_rate as u32,
|
||||
bit_depth: 32,
|
||||
monitor: monitor.clone(),
|
||||
}),
|
||||
@@ -194,7 +204,7 @@ impl MonitorHandle {
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
pub(crate) refresh_rate_millihertz: u32,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
@@ -210,8 +220,8 @@ impl VideoMode {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
|
||||
@@ -7,7 +7,7 @@ use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use sctk::reexports::calloop::{LoopHandle, RegistrationToken};
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
|
||||
use sctk::seat::keyboard;
|
||||
|
||||
@@ -20,12 +20,6 @@ mod keymap;
|
||||
|
||||
pub(crate) struct Keyboard {
|
||||
pub keyboard: WlKeyboard,
|
||||
|
||||
/// The source for repeat keys.
|
||||
pub repeat_token: Option<RegistrationToken>,
|
||||
|
||||
/// LoopHandle to drop `RepeatSource`, when dropping the keyboard.
|
||||
pub loop_handle: LoopHandle<'static, WinitState>,
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
@@ -35,7 +29,7 @@ impl Keyboard {
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Option<Self> {
|
||||
let mut inner = KeyboardInner::new(modifiers_state);
|
||||
let keyboard_data = keyboard::map_keyboard_repeat(
|
||||
let keyboard = keyboard::map_keyboard_repeat(
|
||||
loop_handle.clone(),
|
||||
seat,
|
||||
None,
|
||||
@@ -44,15 +38,10 @@ impl Keyboard {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_keyboard(event, &mut inner, winit_state);
|
||||
},
|
||||
);
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
let (keyboard, repeat_token) = keyboard_data.ok()?;
|
||||
|
||||
Some(Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
repeat_token: Some(repeat_token),
|
||||
})
|
||||
Some(Self { keyboard })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +50,6 @@ impl Drop for Keyboard {
|
||||
if self.keyboard.as_ref().version() >= 3 {
|
||||
self.keyboard.release();
|
||||
}
|
||||
|
||||
if let Some(repeat_token) = self.repeat_token.take() {
|
||||
self.loop_handle.remove(repeat_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1};
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
|
||||
use crate::event::{ModifiersState, TouchPhase};
|
||||
|
||||
@@ -25,10 +26,14 @@ pub(super) struct PointerData {
|
||||
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
|
||||
/// A latest event serial.
|
||||
/// Latest observed serial in pointer events.
|
||||
pub latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Latest observed serial in pointer enter events.
|
||||
pub latest_enter_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// The currently accumulated axis data on a pointer.
|
||||
pub axis_data: AxisData,
|
||||
}
|
||||
@@ -36,13 +41,16 @@ pub(super) struct PointerData {
|
||||
impl PointerData {
|
||||
pub fn new(
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
surface: None,
|
||||
latest_serial: Rc::new(Cell::new(0)),
|
||||
latest_enter_serial: Rc::new(Cell::new(0)),
|
||||
confined_pointer,
|
||||
locked_pointer,
|
||||
modifiers_state,
|
||||
pointer_constraints,
|
||||
axis_data: AxisData::new(),
|
||||
|
||||
@@ -42,6 +42,7 @@ pub(super) fn handle_pointer(
|
||||
..
|
||||
} => {
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
pointer_data.latest_enter_serial.replace(serial);
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
if !winit_state.window_map.contains_key(&window_id) {
|
||||
@@ -59,8 +60,10 @@ pub(super) fn handle_pointer(
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_entered(winit_pointer);
|
||||
@@ -102,8 +105,10 @@ pub(super) fn handle_pointer(
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_left(winit_pointer);
|
||||
@@ -193,9 +198,9 @@ pub(super) fn handle_pointer(
|
||||
|
||||
// Old seat compatibility.
|
||||
match axis {
|
||||
// Wayland vertical sign convention is the inverse of winit.
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -216,9 +221,9 @@ pub(super) fn handle_pointer(
|
||||
} else {
|
||||
let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// Wayland vertical sign convention is the inverse of winit.
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -237,9 +242,9 @@ pub(super) fn handle_pointer(
|
||||
.unwrap_or((0., 0.));
|
||||
|
||||
match axis {
|
||||
// Wayland vertical sign convention is the inverse of winit.
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += discrete as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= discrete as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -297,9 +302,17 @@ pub(super) fn handle_pointer(
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
|
||||
if let RelativePointerEvent::RelativeMotion { dx, dy, .. } = event {
|
||||
winit_state
|
||||
.event_sink
|
||||
.push_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
|
||||
if let RelativePointerEvent::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
winit_state.event_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
DeviceId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,14 @@ use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_rela
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
|
||||
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
|
||||
use sctk::window::{FallbackFrame, Window};
|
||||
use sctk::window::Window;
|
||||
|
||||
use crate::event::ModifiersState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::window::WinitFrame;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
mod data;
|
||||
@@ -34,9 +36,17 @@ pub struct WinitPointer {
|
||||
/// Cursor to handle confine requests.
|
||||
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
/// Cursor to handle locked requests.
|
||||
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
|
||||
/// Latest observed serial in pointer events.
|
||||
/// used by Window::start_interactive_move()
|
||||
latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Latest observed serial in pointer enter events.
|
||||
/// used by Window::set_cursor()
|
||||
latest_enter_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Seat.
|
||||
seat: WlSeat,
|
||||
}
|
||||
@@ -58,7 +68,9 @@ impl WinitPointer {
|
||||
Some(cursor_icon) => cursor_icon,
|
||||
None => {
|
||||
// Hide the cursor.
|
||||
(*self.pointer).set_cursor(self.latest_serial.get(), None, 0, 0);
|
||||
// WlPointer::set_cursor() expects the serial of the last *enter*
|
||||
// event (compare to to start_interactive_move()).
|
||||
(*self.pointer).set_cursor(self.latest_enter_serial.get(), None, 0, 0);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -106,7 +118,7 @@ impl WinitPointer {
|
||||
CursorIcon::ZoomOut => &["zoom-out"],
|
||||
};
|
||||
|
||||
let serial = Some(self.latest_serial.get());
|
||||
let serial = Some(self.latest_enter_serial.get());
|
||||
for cursor in cursors {
|
||||
if self.pointer.set_cursor(cursor, serial).is_ok() {
|
||||
return;
|
||||
@@ -150,7 +162,55 @@ impl WinitPointer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_window(&self, window: &Window<FallbackFrame>) {
|
||||
pub fn lock(&self, surface: &WlSurface) {
|
||||
let pointer_constraints = match &self.pointer_constraints {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
|
||||
pointer_constraints,
|
||||
surface,
|
||||
&*self.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn unlock(&self) {
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut locked_pointer = locked_pointer.borrow_mut();
|
||||
|
||||
if let Some(locked_pointer) = locked_pointer.take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let locked_pointer = locked_pointer.borrow_mut();
|
||||
if let Some(locked_pointer) = locked_pointer.as_ref() {
|
||||
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_window(&self, window: &Window<WinitFrame>) {
|
||||
// WlPointer::setart_interactive_move() expects the last serial of *any*
|
||||
// pointer event (compare to set_cursor()).
|
||||
window.start_interactive_move(&self.seat, self.latest_serial.get());
|
||||
}
|
||||
}
|
||||
@@ -165,6 +225,9 @@ pub(super) struct Pointers {
|
||||
|
||||
/// Confined pointer.
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
/// Locked pointer.
|
||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
}
|
||||
|
||||
impl Pointers {
|
||||
@@ -176,11 +239,15 @@ impl Pointers {
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
let confined_pointer = Rc::new(RefCell::new(None));
|
||||
let locked_pointer = Rc::new(RefCell::new(None));
|
||||
|
||||
let pointer_data = Rc::new(RefCell::new(PointerData::new(
|
||||
confined_pointer.clone(),
|
||||
locked_pointer.clone(),
|
||||
pointer_constraints.clone(),
|
||||
modifiers_state,
|
||||
)));
|
||||
|
||||
let pointer_seat = seat.detach();
|
||||
let pointer = theme_manager.theme_pointer_with_impl(
|
||||
seat,
|
||||
@@ -207,6 +274,7 @@ impl Pointers {
|
||||
pointer,
|
||||
relative_pointer,
|
||||
confined_pointer,
|
||||
locked_pointer,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,6 +291,11 @@ impl Drop for Pointers {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
|
||||
// Drop lock ponter.
|
||||
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
|
||||
// Drop the pointer itself in case it's possible.
|
||||
if self.pointer.as_ref().version() >= 3 {
|
||||
self.pointer.release();
|
||||
@@ -234,7 +307,7 @@ pub(super) fn init_relative_pointer(
|
||||
relative_pointer_manager: &ZwpRelativePointerManagerV1,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpRelativePointerV1 {
|
||||
let relative_pointer = relative_pointer_manager.get_relative_pointer(&*pointer);
|
||||
let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer);
|
||||
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_relative_pointer(event, winit_state);
|
||||
@@ -255,3 +328,16 @@ pub(super) fn init_confined_pointer(
|
||||
|
||||
confined_pointer.detach()
|
||||
}
|
||||
|
||||
pub(super) fn init_locked_pointer(
|
||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpLockedPointerV1 {
|
||||
let locked_pointer =
|
||||
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
|
||||
|
||||
locked_pointer.quick_assign(move |_, _, _| {});
|
||||
|
||||
locked_pointer.detach()
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input
|
||||
Event as TextInputEvent, ZwpTextInputV3,
|
||||
};
|
||||
|
||||
use crate::event::WindowEvent;
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
|
||||
use super::{TextInputHandler, TextInputInner};
|
||||
use super::{Preedit, TextInputHandler, TextInputInner};
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_text_input(
|
||||
@@ -30,8 +30,11 @@ pub(super) fn handle_text_input(
|
||||
inner.target_window_id = Some(window_id);
|
||||
|
||||
// Enable text input on that surface.
|
||||
text_input.enable();
|
||||
text_input.commit();
|
||||
if window_handle.ime_allowed.get() {
|
||||
text_input.enable();
|
||||
text_input.commit();
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
|
||||
}
|
||||
|
||||
// Notify a window we're currently over about text input handler.
|
||||
let text_input_handler = TextInputHandler {
|
||||
@@ -58,19 +61,45 @@ pub(super) fn handle_text_input(
|
||||
text_input: text_input.detach(),
|
||||
};
|
||||
window_handle.text_input_left(text_input_handler);
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
|
||||
}
|
||||
TextInputEvent::PreeditString {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
} => {
|
||||
let cursor_begin = usize::try_from(cursor_begin).ok();
|
||||
let cursor_end = usize::try_from(cursor_end).ok();
|
||||
let text = text.unwrap_or_default();
|
||||
inner.pending_preedit = Some(Preedit {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
});
|
||||
}
|
||||
TextInputEvent::CommitString { text } => {
|
||||
// Update currenly commited string.
|
||||
inner.commit_string = text;
|
||||
// Update currenly commited string and reset previous preedit.
|
||||
inner.pending_preedit = None;
|
||||
inner.pending_commit = Some(text.unwrap_or_default());
|
||||
}
|
||||
TextInputEvent::Done { .. } => {
|
||||
let (window_id, text) = match (inner.target_window_id, inner.commit_string.take()) {
|
||||
(Some(window_id), Some(text)) => (window_id, text),
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
for ch in text.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
if let Some(text) = inner.pending_commit.take() {
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
|
||||
}
|
||||
|
||||
// Push preedit string we've got after latest commit.
|
||||
if let Some(preedit) = inner.pending_preedit.take() {
|
||||
let cursor_range = preedit
|
||||
.cursor_begin
|
||||
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
|
||||
|
||||
let event = Ime::Preedit(preedit.text, cursor_range);
|
||||
event_sink.push_window_event(WindowEvent::Ime(event), window_id);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -20,6 +20,17 @@ impl TextInputHandler {
|
||||
self.text_input.set_cursor_rectangle(x, y, 0, 0);
|
||||
self.text_input.commit();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_input_allowed(&self, allowed: bool) {
|
||||
if allowed {
|
||||
self.text_input.enable();
|
||||
} else {
|
||||
self.text_input.disable();
|
||||
}
|
||||
|
||||
self.text_input.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around text input to automatically destroy the object on `Drop`.
|
||||
@@ -52,15 +63,25 @@ struct TextInputInner {
|
||||
/// Currently focused surface.
|
||||
target_window_id: Option<WindowId>,
|
||||
|
||||
/// Pending string to commit.
|
||||
commit_string: Option<String>,
|
||||
/// Pending commit event which will be dispatched on `text_input_v3::Done`.
|
||||
pending_commit: Option<String>,
|
||||
|
||||
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
|
||||
pending_preedit: Option<Preedit>,
|
||||
}
|
||||
|
||||
struct Preedit {
|
||||
text: String,
|
||||
cursor_begin: Option<usize>,
|
||||
cursor_end: Option<usize>,
|
||||
}
|
||||
|
||||
impl TextInputInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
target_window_id: None,
|
||||
commit_string: None,
|
||||
pending_commit: None,
|
||||
pending_preedit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,15 @@ pub(super) fn handle_touch(
|
||||
window_id,
|
||||
);
|
||||
|
||||
inner
|
||||
.touch_points
|
||||
.push(TouchPoint::new(surface, position, id));
|
||||
// For `TouchEvent::Up` we don't receive a position, so we're tracking active
|
||||
// touch points. Update either a known touch id or register a new one.
|
||||
if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) {
|
||||
inner.touch_points[i].position = position;
|
||||
} else {
|
||||
inner
|
||||
.touch_points
|
||||
.push(TouchPoint::new(surface, position, id));
|
||||
}
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {
|
||||
|
||||
@@ -7,8 +7,10 @@ use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
|
||||
use raw_window_handle::WaylandHandle;
|
||||
use sctk::window::{Decorations, FallbackFrame};
|
||||
use raw_window_handle::{
|
||||
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
|
||||
};
|
||||
use sctk::window::Decorations;
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
@@ -17,7 +19,9 @@ use crate::platform_impl::{
|
||||
MonitorHandle as PlatformMonitorHandle, OsError,
|
||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
};
|
||||
use crate::window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes};
|
||||
use crate::window::{
|
||||
CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes,
|
||||
};
|
||||
|
||||
use super::env::WindowingFeatures;
|
||||
use super::event_loop::WinitState;
|
||||
@@ -28,6 +32,14 @@ pub mod shim;
|
||||
|
||||
use shim::{WindowHandle, WindowRequest, WindowUpdate};
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
pub type WinitFrame = sctk_adwaita::AdwaitaFrame;
|
||||
#[cfg(not(feature = "sctk-adwaita"))]
|
||||
pub type WinitFrame = sctk::window::FallbackFrame;
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
const WAYLAND_CSD_THEME_ENV_VAR: &str = "WINIT_WAYLAND_CSD_THEME";
|
||||
|
||||
pub struct Window {
|
||||
/// Window id.
|
||||
window_id: WindowId,
|
||||
@@ -58,10 +70,19 @@ pub struct Window {
|
||||
|
||||
/// Requests that SCTK window should perform.
|
||||
window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
|
||||
/// Whether the window is resizeable.
|
||||
resizeable: AtomicBool,
|
||||
|
||||
/// Whether the window is decorated.
|
||||
decorated: AtomicBool,
|
||||
|
||||
/// Grabbing mode.
|
||||
cursor_grab_mode: Mutex<CursorGrabMode>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
pub(crate) fn new<T>(
|
||||
event_loop_window_target: &EventLoopWindowTarget<T>,
|
||||
attributes: WindowAttributes,
|
||||
platform_attributes: PlatformAttributes,
|
||||
@@ -71,7 +92,7 @@ impl Window {
|
||||
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
|
||||
// Get the window that receiced the event.
|
||||
// Get the window that received the event.
|
||||
let window_id = super::make_wid(&surface);
|
||||
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
|
||||
|
||||
@@ -87,7 +108,7 @@ impl Window {
|
||||
|
||||
let window_id = super::make_wid(&surface);
|
||||
let maximized = Arc::new(AtomicBool::new(false));
|
||||
let maximzied_clone = maximized.clone();
|
||||
let maximized_clone = maximized.clone();
|
||||
let fullscreen = Arc::new(AtomicBool::new(false));
|
||||
let fullscreen_clone = fullscreen.clone();
|
||||
|
||||
@@ -99,7 +120,7 @@ impl Window {
|
||||
let theme_manager = event_loop_window_target.theme_manager.clone();
|
||||
let mut window = event_loop_window_target
|
||||
.env
|
||||
.create_window::<FallbackFrame, _>(
|
||||
.create_window::<WinitFrame, _>(
|
||||
surface.clone(),
|
||||
Some(theme_manager),
|
||||
(width, height),
|
||||
@@ -115,7 +136,7 @@ impl Window {
|
||||
}
|
||||
Event::Configure { new_size, states } => {
|
||||
let is_maximized = states.contains(&State::Maximized);
|
||||
maximzied_clone.store(is_maximized, Ordering::Relaxed);
|
||||
maximized_clone.store(is_maximized, Ordering::Relaxed);
|
||||
let is_fullscreen = states.contains(&State::Fullscreen);
|
||||
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
|
||||
|
||||
@@ -133,18 +154,26 @@ impl Window {
|
||||
)
|
||||
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
|
||||
|
||||
// Set CSD frame config
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
{
|
||||
let theme = platform_attributes.csd_theme.unwrap_or_else(|| {
|
||||
let env = std::env::var(WAYLAND_CSD_THEME_ENV_VAR).unwrap_or_default();
|
||||
match env.to_lowercase().as_str() {
|
||||
"dark" => Theme::Dark,
|
||||
_ => Theme::Light,
|
||||
}
|
||||
});
|
||||
|
||||
window.set_frame_config(theme.into());
|
||||
}
|
||||
|
||||
// Set decorations.
|
||||
if attributes.decorations {
|
||||
window.set_decorate(Decorations::FollowServer);
|
||||
} else {
|
||||
window.set_decorate(Decorations::None);
|
||||
}
|
||||
// Without this commit here at least on kwin 5.23.3 the initial configure
|
||||
// will have a size (1,1), the second configure including the decoration
|
||||
// mode will have the min_size as its size. With this commit the initial
|
||||
// configure will have no size, the application will draw it's content
|
||||
// with the initial size and everything works as expected afterwards.
|
||||
window.surface().commit();
|
||||
|
||||
// Min dimensions.
|
||||
let min_size = attributes
|
||||
@@ -159,8 +188,8 @@ impl Window {
|
||||
window.set_max_size(max_size);
|
||||
|
||||
// Set Wayland specific window attributes.
|
||||
if let Some(app_id) = platform_attributes.app_id {
|
||||
window.set_app_id(app_id);
|
||||
if let Some(name) = platform_attributes.name {
|
||||
window.set_app_id(name.general);
|
||||
}
|
||||
|
||||
// Set common window attributes.
|
||||
@@ -192,6 +221,16 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
// Without this commit here at least on kwin 5.23.3 the initial configure
|
||||
// will have a size (1,1), the second configure including the decoration
|
||||
// mode will have the min_size as its size. With this commit the initial
|
||||
// configure will have no size, the application will draw it's content
|
||||
// with the initial size and everything works as expected afterwards.
|
||||
//
|
||||
// The window commit must be after setting on top level properties, but right before any
|
||||
// buffer attachments commits.
|
||||
window.surface().commit();
|
||||
|
||||
let size = Arc::new(Mutex::new(LogicalSize::new(width, height)));
|
||||
|
||||
// We should trigger redraw and commit the surface for the newly created window.
|
||||
@@ -210,10 +249,19 @@ impl Window {
|
||||
window_requests.clone(),
|
||||
);
|
||||
|
||||
// Set resizable state, so we can determine how to handle `Window::set_inner_size`.
|
||||
window_handle.is_resizable.set(attributes.resizable);
|
||||
|
||||
let mut winit_state = event_loop_window_target.state.borrow_mut();
|
||||
|
||||
winit_state.window_map.insert(window_id, window_handle);
|
||||
|
||||
// On Wayland window doesn't have Focus by default and it'll get it later on. So be
|
||||
// explicit here.
|
||||
winit_state
|
||||
.event_sink
|
||||
.push_window_event(crate::event::WindowEvent::Focused(false), window_id);
|
||||
|
||||
winit_state
|
||||
.window_updates
|
||||
.insert(window_id, WindowUpdate::new());
|
||||
@@ -248,6 +296,9 @@ impl Window {
|
||||
fullscreen,
|
||||
maximized,
|
||||
windowing_features,
|
||||
resizeable: AtomicBool::new(attributes.resizable),
|
||||
decorated: AtomicBool::new(attributes.decorations),
|
||||
cursor_grab_mode: Mutex::new(CursorGrabMode::None),
|
||||
};
|
||||
|
||||
Ok(window)
|
||||
@@ -270,6 +321,11 @@ impl Window {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
@@ -333,9 +389,15 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.resizeable.store(resizable, Ordering::Relaxed);
|
||||
self.send_request(WindowRequest::Resizeable(resizable));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
self.resizeable.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> u32 {
|
||||
// The scale factor from `get_surface_scale_factor` is always greater than zero, so
|
||||
@@ -345,9 +407,20 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
self.decorated.store(decorate, Ordering::Relaxed);
|
||||
self.send_request(WindowRequest::Decorate(decorate));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
self.decorated.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_csd_theme(&self, theme: Theme) {
|
||||
self.send_request(WindowRequest::CsdThemeVariant(theme));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
// You can't unminimize the window on Wayland.
|
||||
@@ -415,12 +488,17 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
if !self.windowing_features.cursor_grab() {
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
if !self.windowing_features.pointer_constraints() {
|
||||
if mode == CursorGrabMode::None {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
||||
}
|
||||
|
||||
self.send_request(WindowRequest::GrabCursor(grab));
|
||||
*self.cursor_grab_mode.lock().unwrap() = mode;
|
||||
self.send_request(WindowRequest::SetCursorGrabMode(mode));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -435,15 +513,19 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> {
|
||||
// XXX This is possible if the locked pointer is being used. We don't have any
|
||||
// API for that right now, but it could be added in
|
||||
// https://github.com/rust-windowing/winit/issues/1677.
|
||||
//
|
||||
// This function is essential for the locked pointer API.
|
||||
//
|
||||
// See pointer-constraints-unstable-v1.xml.
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
||||
// Positon can be set only for locked cursor.
|
||||
if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked {
|
||||
return Err(ExternalError::Os(os_error!(OsError::WaylandMisc(
|
||||
"cursor position can be set only for locked cursor."
|
||||
))));
|
||||
}
|
||||
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.send_request(WindowRequest::SetLockedCursorPosition(position));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -453,11 +535,23 @@ impl Window {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
self.send_request(WindowRequest::PassthroughMouseInput(!hittest));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let position = position.to_logical(scale_factor);
|
||||
self.send_request(WindowRequest::IMEPosition(position));
|
||||
self.send_request(WindowRequest::ImePosition(position));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
self.send_request(WindowRequest::AllowIme(allowed));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -487,11 +581,17 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> WaylandHandle {
|
||||
let mut handle = WaylandHandle::empty();
|
||||
handle.display = self.display.get_display_ptr() as *mut _;
|
||||
handle.surface = self.surface.as_ref().c_ptr() as *mut _;
|
||||
handle
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut window_handle = WaylandWindowHandle::empty();
|
||||
window_handle.surface = self.surface.as_ref().c_ptr() as *mut _;
|
||||
RawWindowHandle::Wayland(window_handle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
display_handle.display = self.display.get_display_ptr() as *mut _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -506,3 +606,13 @@ impl Drop for Window {
|
||||
self.send_request(WindowRequest::Close);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
impl From<Theme> for sctk_adwaita::FrameConfig {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => sctk_adwaita::FrameConfig::light(),
|
||||
Theme::Dark => sctk_adwaita::FrameConfig::dark(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
use std::cell::Cell;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::window::{Decorations, FallbackFrame, Window};
|
||||
use sctk::window::{Decorations, Window};
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
use crate::event::WindowEvent;
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::env::WinitEnv;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
|
||||
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
|
||||
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
use crate::window::{CursorIcon, UserAttentionType};
|
||||
use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType};
|
||||
|
||||
use super::WinitFrame;
|
||||
|
||||
/// A request to SCTK window from Winit window.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -37,8 +41,11 @@ pub enum WindowRequest {
|
||||
/// Change the cursor icon.
|
||||
NewCursorIcon(CursorIcon),
|
||||
|
||||
/// Grab cursor.
|
||||
GrabCursor(bool),
|
||||
/// Change cursor grabbing mode.
|
||||
SetCursorGrabMode(CursorGrabMode),
|
||||
|
||||
/// Set cursor position.
|
||||
SetLockedCursorPosition(LogicalPosition<u32>),
|
||||
|
||||
/// Drag window.
|
||||
DragWindow,
|
||||
@@ -52,6 +59,9 @@ pub enum WindowRequest {
|
||||
/// Request decorations change.
|
||||
Decorate(bool),
|
||||
|
||||
/// Request decorations change.
|
||||
CsdThemeVariant(Theme),
|
||||
|
||||
/// Make the window resizeable.
|
||||
Resizeable(bool),
|
||||
|
||||
@@ -68,13 +78,19 @@ pub enum WindowRequest {
|
||||
FrameSize(LogicalSize<u32>),
|
||||
|
||||
/// Set IME window position.
|
||||
IMEPosition(LogicalPosition<u32>),
|
||||
ImePosition(LogicalPosition<u32>),
|
||||
|
||||
/// Enable IME on the given window.
|
||||
AllowIme(bool),
|
||||
|
||||
/// Request Attention.
|
||||
///
|
||||
/// `None` unsets the attention request.
|
||||
Attention(Option<UserAttentionType>),
|
||||
|
||||
/// Passthrough mouse input to underlying windows.
|
||||
PassthroughMouseInput(bool),
|
||||
|
||||
/// Redraw was requested.
|
||||
Redraw,
|
||||
|
||||
@@ -139,7 +155,7 @@ impl WindowUpdate {
|
||||
/// and react to events.
|
||||
pub struct WindowHandle {
|
||||
/// An actual window.
|
||||
pub window: Window<FallbackFrame>,
|
||||
pub window: ManuallyDrop<Window<WinitFrame>>,
|
||||
|
||||
/// The current size of the window.
|
||||
pub size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
@@ -150,11 +166,17 @@ pub struct WindowHandle {
|
||||
/// Current cursor icon.
|
||||
pub cursor_icon: Cell<CursorIcon>,
|
||||
|
||||
/// Whether the window is resizable.
|
||||
pub is_resizable: Cell<bool>,
|
||||
|
||||
/// Allow IME events for that window.
|
||||
pub ime_allowed: Cell<bool>,
|
||||
|
||||
/// Visible cursor or not.
|
||||
cursor_visible: Cell<bool>,
|
||||
|
||||
/// Cursor confined to the surface.
|
||||
confined: Cell<bool>,
|
||||
cursor_grab_mode: Cell<CursorGrabMode>,
|
||||
|
||||
/// Pointers over the current surface.
|
||||
pointers: Vec<WinitPointer>,
|
||||
@@ -167,49 +189,71 @@ pub struct WindowHandle {
|
||||
|
||||
/// Indicator whether user attention is requested.
|
||||
attention_requested: Cell<bool>,
|
||||
|
||||
/// Compositor
|
||||
compositor: Attached<WlCompositor>,
|
||||
}
|
||||
|
||||
impl WindowHandle {
|
||||
pub fn new(
|
||||
env: &Environment<WinitEnv>,
|
||||
window: Window<FallbackFrame>,
|
||||
window: Window<WinitFrame>,
|
||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
) -> Self {
|
||||
let xdg_activation = env.get_global::<XdgActivationV1>();
|
||||
// Unwrap is safe, since we can't create window without compositor anyway and won't be
|
||||
// here.
|
||||
let compositor = env.get_global::<WlCompositor>().unwrap();
|
||||
|
||||
Self {
|
||||
window,
|
||||
window: ManuallyDrop::new(window),
|
||||
size,
|
||||
pending_window_requests,
|
||||
cursor_icon: Cell::new(CursorIcon::Default),
|
||||
confined: Cell::new(false),
|
||||
is_resizable: Cell::new(true),
|
||||
cursor_grab_mode: Cell::new(CursorGrabMode::None),
|
||||
cursor_visible: Cell::new(true),
|
||||
pointers: Vec::new(),
|
||||
text_inputs: Vec::new(),
|
||||
xdg_activation,
|
||||
attention_requested: Cell::new(false),
|
||||
compositor,
|
||||
ime_allowed: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, grab: bool) {
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
|
||||
// The new requested state matches the current confine status, return.
|
||||
if self.confined.get() == grab {
|
||||
let old_mode = self.cursor_grab_mode.replace(mode);
|
||||
if old_mode == mode {
|
||||
return;
|
||||
}
|
||||
|
||||
self.confined.replace(grab);
|
||||
// Clear old pointer data.
|
||||
match old_mode {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
|
||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
|
||||
}
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
if self.confined.get() {
|
||||
let surface = self.window.surface();
|
||||
pointer.confine(surface);
|
||||
} else {
|
||||
pointer.unconfine();
|
||||
let surface = self.window.surface();
|
||||
match mode {
|
||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
|
||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
|
||||
CursorGrabMode::None => {
|
||||
// Current lock/confine was already removed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
|
||||
// XXX the cursor locking is ensured inside `Window`.
|
||||
self.pointers
|
||||
.iter()
|
||||
.for_each(|p| p.set_cursor_position(position.x, position.y));
|
||||
}
|
||||
|
||||
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
||||
None => return,
|
||||
@@ -257,10 +301,13 @@ impl WindowHandle {
|
||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
||||
|
||||
if position.is_none() {
|
||||
if self.confined.get() {
|
||||
let surface = self.window.surface();
|
||||
pointer.confine(surface);
|
||||
let surface = self.window.surface();
|
||||
match self.cursor_grab_mode.get() {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Locked => pointer.lock(surface),
|
||||
CursorGrabMode::Confined => pointer.confine(surface),
|
||||
}
|
||||
|
||||
self.pointers.push(pointer);
|
||||
}
|
||||
|
||||
@@ -275,9 +322,11 @@ impl WindowHandle {
|
||||
if let Some(position) = position {
|
||||
let pointer = self.pointers.remove(position);
|
||||
|
||||
// Drop the confined pointer.
|
||||
if self.confined.get() {
|
||||
pointer.unconfine();
|
||||
// Drop the grabbing mode.
|
||||
match self.cursor_grab_mode.get() {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Locked => pointer.unlock(),
|
||||
CursorGrabMode::Confined => pointer.unconfine(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,6 +353,41 @@ impl WindowHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
|
||||
if passthrough_mouse_input {
|
||||
let region = self.compositor.create_region();
|
||||
region.add(0, 0, 0, 0);
|
||||
self.window
|
||||
.surface()
|
||||
.set_input_region(Some(®ion.detach()));
|
||||
region.destroy();
|
||||
} else {
|
||||
// Using `None` results in the entire window being clickable.
|
||||
self.window.surface().set_input_region(None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
|
||||
if self.ime_allowed.get() == allowed {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ime_allowed.replace(allowed);
|
||||
let window_id = wayland::make_wid(self.window.surface());
|
||||
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_input_allowed(allowed);
|
||||
}
|
||||
|
||||
let event = if allowed {
|
||||
WindowEvent::Ime(Ime::Enabled)
|
||||
} else {
|
||||
WindowEvent::Ime(Ime::Disabled)
|
||||
};
|
||||
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.cursor_visible.replace(visible);
|
||||
let cursor_icon = match visible {
|
||||
@@ -344,7 +428,8 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
// Process the rest of the events.
|
||||
for (window_id, window_handle) in window_map.iter_mut() {
|
||||
let mut requests = window_handle.pending_window_requests.lock().unwrap();
|
||||
for request in requests.drain(..) {
|
||||
let requests = requests.drain(..);
|
||||
for request in requests {
|
||||
match request {
|
||||
WindowRequest::Fullscreen(fullscreen) => {
|
||||
window_handle.window.set_fullscreen(fullscreen.as_ref());
|
||||
@@ -358,11 +443,18 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
WindowRequest::NewCursorIcon(cursor_icon) => {
|
||||
window_handle.set_cursor_icon(cursor_icon);
|
||||
}
|
||||
WindowRequest::IMEPosition(position) => {
|
||||
WindowRequest::ImePosition(position) => {
|
||||
window_handle.set_ime_position(position);
|
||||
}
|
||||
WindowRequest::GrabCursor(grab) => {
|
||||
window_handle.set_cursor_grab(grab);
|
||||
WindowRequest::AllowIme(allow) => {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
window_handle.set_ime_allowed(allow, event_sink);
|
||||
}
|
||||
WindowRequest::SetCursorGrabMode(mode) => {
|
||||
window_handle.set_cursor_grab(mode);
|
||||
}
|
||||
WindowRequest::SetLockedCursorPosition(position) => {
|
||||
window_handle.set_locked_cursor_position(position);
|
||||
}
|
||||
WindowRequest::DragWindow => {
|
||||
window_handle.drag_window();
|
||||
@@ -389,6 +481,15 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
WindowRequest::CsdThemeVariant(theme) => {
|
||||
window_handle.window.set_frame_config(theme.into());
|
||||
|
||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
#[cfg(not(feature = "sctk-adwaita"))]
|
||||
WindowRequest::CsdThemeVariant(_) => {}
|
||||
WindowRequest::Resizeable(resizeable) => {
|
||||
window_handle.window.set_resizable(resizeable);
|
||||
|
||||
@@ -418,13 +519,26 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
window_update.redraw_requested = true;
|
||||
}
|
||||
WindowRequest::FrameSize(size) => {
|
||||
// Set new size.
|
||||
if !window_handle.is_resizable.get() {
|
||||
// On Wayland non-resizable window is achieved by setting both min and max
|
||||
// size of the window to the same value.
|
||||
let size = Some((size.width, size.height));
|
||||
window_handle.window.set_max_size(size);
|
||||
window_handle.window.set_min_size(size);
|
||||
}
|
||||
|
||||
window_handle.window.resize(size.width, size.height);
|
||||
|
||||
// We should refresh the frame after resize.
|
||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::PassthroughMouseInput(passthrough) => {
|
||||
window_handle.passthrough_mouse_input(passthrough);
|
||||
|
||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Attention(request_type) => {
|
||||
window_handle.set_user_attention(request_type);
|
||||
}
|
||||
@@ -450,3 +564,14 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
let _ = window_updates.remove(&window);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let surface = self.window.surface().clone();
|
||||
// The window must be destroyed before wl_surface.
|
||||
ManuallyDrop::drop(&mut self.window);
|
||||
surface.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,20 +12,23 @@ use super::{
|
||||
|
||||
use util::modifiers::{ModifierKeyState, ModifierKeymap};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::{
|
||||
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent,
|
||||
DeviceEvent, ElementState, Event, Ime, KeyboardInput, ModifiersState, TouchPhase,
|
||||
WindowEvent,
|
||||
},
|
||||
event_loop::EventLoopWindowTarget as RootELW,
|
||||
};
|
||||
|
||||
/// The X11 documentation states: "Keycodes lie in the inclusive range [8,255]".
|
||||
/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`".
|
||||
const KEYCODE_OFFSET: u8 = 8;
|
||||
|
||||
pub(super) struct EventProcessor<T: 'static> {
|
||||
pub(super) dnd: Dnd,
|
||||
pub(super) ime_receiver: ImeReceiver,
|
||||
pub(super) ime_event_receiver: ImeEventReceiver,
|
||||
pub(super) randr_event_offset: c_int,
|
||||
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
|
||||
pub(super) xi2ext: XExtension,
|
||||
@@ -37,6 +40,7 @@ pub(super) struct EventProcessor<T: 'static> {
|
||||
pub(super) first_touch: Option<u64>,
|
||||
// Currently focused window belonging to this process
|
||||
pub(super) active_window: Option<ffi::Window>,
|
||||
pub(super) is_composing: bool,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventProcessor<T> {
|
||||
@@ -45,7 +49,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
let mut devices = self.devices.borrow_mut();
|
||||
if let Some(info) = DeviceInfo::get(&wt.xconn, device) {
|
||||
for info in info.iter() {
|
||||
devices.insert(DeviceId(info.deviceid), Device::new(&self, info));
|
||||
devices.insert(DeviceId(info.deviceid), Device::new(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +59,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
F: Fn(&Arc<UnownedWindow>) -> Ret,
|
||||
{
|
||||
let mut deleted = false;
|
||||
let window_id = WindowId(window_id);
|
||||
let window_id = WindowId(window_id as u64);
|
||||
let wt = get_xtarget(&self.target);
|
||||
let result = wt
|
||||
.windows
|
||||
@@ -410,7 +414,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// resizing by dragging across monitors *without* dropping the window.
|
||||
let (width, height) = shared_state_lock
|
||||
.dpi_adjusted
|
||||
.unwrap_or_else(|| (xev.width as u32, xev.height as u32));
|
||||
.unwrap_or((xev.width as u32, xev.height as u32));
|
||||
|
||||
let last_scale_factor = shared_state_lock.last_monitor.scale_factor;
|
||||
let new_scale_factor = {
|
||||
@@ -509,7 +513,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
// In the event that the window's been destroyed without being dropped first, we
|
||||
// cleanup again here.
|
||||
wt.windows.borrow_mut().remove(&WindowId(window));
|
||||
wt.windows.borrow_mut().remove(&WindowId(window as u64));
|
||||
|
||||
// Since all XIM stuff needs to happen from the same thread, we destroy the input
|
||||
// context here instead of when dropping the window.
|
||||
@@ -527,8 +531,13 @@ impl<T: 'static> EventProcessor<T> {
|
||||
ffi::VisibilityNotify => {
|
||||
let xev: &ffi::XVisibilityEvent = xev.as_ref();
|
||||
let xwindow = xev.window;
|
||||
|
||||
self.with_window(xwindow, |window| window.visibility_notify());
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(xwindow),
|
||||
event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured),
|
||||
});
|
||||
self.with_window(xwindow, |window| {
|
||||
window.visibility_notify();
|
||||
});
|
||||
}
|
||||
|
||||
ffi::Expose => {
|
||||
@@ -567,7 +576,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
|
||||
// a keycode of 0.
|
||||
if keycode != 0 {
|
||||
if keycode != 0 && !self.is_composing {
|
||||
let scancode = keycode - KEYCODE_OFFSET as u32;
|
||||
let keysym = wt.xconn.lookup_keysym(xkev);
|
||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
||||
@@ -602,12 +611,25 @@ impl<T: 'static> EventProcessor<T> {
|
||||
return;
|
||||
};
|
||||
|
||||
for chr in written.chars() {
|
||||
// If we're composing right now, send the string we've got from X11 via
|
||||
// Ime::Commit.
|
||||
if self.is_composing && keycode == 0 && !written.is_empty() {
|
||||
let event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ReceivedCharacter(chr),
|
||||
event: WindowEvent::Ime(Ime::Commit(written)),
|
||||
};
|
||||
|
||||
self.is_composing = false;
|
||||
callback(event);
|
||||
} else {
|
||||
for chr in written.chars() {
|
||||
let event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ReceivedCharacter(chr),
|
||||
};
|
||||
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -693,8 +715,8 @@ impl<T: 'static> EventProcessor<T> {
|
||||
delta: match xev.detail {
|
||||
4 => LineDelta(0.0, 1.0),
|
||||
5 => LineDelta(0.0, -1.0),
|
||||
6 => LineDelta(-1.0, 0.0),
|
||||
7 => LineDelta(1.0, 0.0),
|
||||
6 => LineDelta(1.0, 0.0),
|
||||
7 => LineDelta(-1.0, 0.0),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
phase: TouchPhase::Moved,
|
||||
@@ -774,10 +796,10 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: MouseWheel {
|
||||
device_id,
|
||||
delta: match info.orientation {
|
||||
ScrollOrientation::Horizontal => {
|
||||
LineDelta(delta as f32, 0.0)
|
||||
}
|
||||
// X11 vertical scroll coordinates are opposite to winit's
|
||||
ScrollOrientation::Horizontal => {
|
||||
LineDelta(-delta as f32, 0.0)
|
||||
}
|
||||
ScrollOrientation::Vertical => {
|
||||
LineDelta(0.0, -delta as f32)
|
||||
}
|
||||
@@ -890,6 +912,8 @@ impl<T: 'static> EventProcessor<T> {
|
||||
if self.active_window != Some(xev.event) {
|
||||
self.active_window = Some(xev.event);
|
||||
|
||||
wt.update_device_event_filter(true);
|
||||
|
||||
let window_id = mkwid(xev.event);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
@@ -925,7 +949,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
// Issue key press events for all pressed keys
|
||||
Self::handle_pressed_keys(
|
||||
&wt,
|
||||
wt,
|
||||
window_id,
|
||||
ElementState::Pressed,
|
||||
&self.mod_keymap,
|
||||
@@ -939,6 +963,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
if !self.window_exists(xev.event) {
|
||||
return;
|
||||
}
|
||||
|
||||
wt.ime
|
||||
.borrow_mut()
|
||||
.unfocus(xev.event)
|
||||
@@ -947,9 +972,11 @@ impl<T: 'static> EventProcessor<T> {
|
||||
if self.active_window.take() == Some(xev.event) {
|
||||
let window_id = mkwid(xev.event);
|
||||
|
||||
wt.update_device_event_filter(false);
|
||||
|
||||
// Issue key release events for all pressed keys
|
||||
Self::handle_pressed_keys(
|
||||
&wt,
|
||||
wt,
|
||||
window_id,
|
||||
ElementState::Released,
|
||||
&self.mod_keymap,
|
||||
@@ -1179,20 +1206,19 @@ impl<T: 'static> EventProcessor<T> {
|
||||
if monitor.name == new_monitor.name {
|
||||
let (width, height) = window.inner_size_physical();
|
||||
let (new_width, new_height) = window.adjust_for_dpi(
|
||||
// If there all monitors are closed before, scale
|
||||
// factor would be already changed to 1.0.
|
||||
maybe_prev_scale_factor.unwrap_or(1.0),
|
||||
// If we couldn't determine the previous scale
|
||||
// factor (e.g., because all monitors were closed
|
||||
// before), just pick whatever the current monitor
|
||||
// has set as a baseline.
|
||||
maybe_prev_scale_factor
|
||||
.unwrap_or(monitor.scale_factor),
|
||||
new_monitor.scale_factor,
|
||||
width,
|
||||
height,
|
||||
&*window.shared_state.lock(),
|
||||
);
|
||||
|
||||
let window_id = crate::window::WindowId(
|
||||
crate::platform_impl::platform::WindowId::X(
|
||||
*window_id,
|
||||
),
|
||||
);
|
||||
let window_id = crate::window::WindowId(*window_id);
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let mut new_inner_size =
|
||||
PhysicalSize::new(new_width, new_height);
|
||||
@@ -1220,11 +1246,61 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
match self.ime_receiver.try_recv() {
|
||||
Ok((window_id, x, y)) => {
|
||||
wt.ime.borrow_mut().send_xim_spot(window_id, x, y);
|
||||
// Handle IME requests.
|
||||
if let Ok(request) = self.ime_receiver.try_recv() {
|
||||
let mut ime = wt.ime.borrow_mut();
|
||||
match request {
|
||||
ImeRequest::Position(window_id, x, y) => {
|
||||
ime.send_xim_spot(window_id, x, y);
|
||||
}
|
||||
ImeRequest::Allow(window_id, allowed) => {
|
||||
ime.set_ime_allowed(window_id, allowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (window, event) = match self.ime_event_receiver.try_recv() {
|
||||
Ok((window, event)) => (window, event),
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
match event {
|
||||
ImeEvent::Enabled => {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Enabled),
|
||||
});
|
||||
}
|
||||
ImeEvent::Start => {
|
||||
self.is_composing = true;
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)),
|
||||
});
|
||||
}
|
||||
ImeEvent::Update(text, position) => {
|
||||
if self.is_composing {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))),
|
||||
});
|
||||
}
|
||||
}
|
||||
ImeEvent::End => {
|
||||
self.is_composing = false;
|
||||
// Issue empty preedit on `Done`.
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
});
|
||||
}
|
||||
ImeEvent::Disabled => {
|
||||
self.is_composing = false;
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Disabled),
|
||||
});
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
//ffi::XK_Linefeed => VirtualKeyCode::Linefeed,
|
||||
//ffi::XK_Clear => VirtualKeyCode::Clear,
|
||||
ffi::XK_Return => VirtualKeyCode::Return,
|
||||
//ffi::XK_Pause => VirtualKeyCode::Pause,
|
||||
ffi::XK_Pause => VirtualKeyCode::Pause,
|
||||
//ffi::XK_Scroll_Lock => VirtualKeyCode::Scroll_lock,
|
||||
//ffi::XK_Sys_Req => VirtualKeyCode::Sys_req,
|
||||
ffi::XK_Escape => VirtualKeyCode::Escape,
|
||||
@@ -59,10 +59,10 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
//ffi::XK_Break => VirtualKeyCode::Break,
|
||||
//ffi::XK_Mode_switch => VirtualKeyCode::Mode_switch,
|
||||
//ffi::XK_script_switch => VirtualKeyCode::Script_switch,
|
||||
//ffi::XK_Num_Lock => VirtualKeyCode::Num_lock,
|
||||
ffi::XK_Num_Lock => VirtualKeyCode::Numlock,
|
||||
//ffi::XK_KP_Space => VirtualKeyCode::Kp_space,
|
||||
//ffi::XK_KP_Tab => VirtualKeyCode::Kp_tab,
|
||||
//ffi::XK_KP_Enter => VirtualKeyCode::Kp_enter,
|
||||
ffi::XK_KP_Enter => VirtualKeyCode::NumpadEnter,
|
||||
//ffi::XK_KP_F1 => VirtualKeyCode::Kp_f1,
|
||||
//ffi::XK_KP_F2 => VirtualKeyCode::Kp_f2,
|
||||
//ffi::XK_KP_F3 => VirtualKeyCode::Kp_f3,
|
||||
@@ -83,7 +83,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals,
|
||||
ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply,
|
||||
ffi::XK_KP_Add => VirtualKeyCode::NumpadAdd,
|
||||
//ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator,
|
||||
ffi::XK_KP_Separator => VirtualKeyCode::NumpadComma,
|
||||
ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract,
|
||||
ffi::XK_KP_Decimal => VirtualKeyCode::NumpadDecimal,
|
||||
ffi::XK_KP_Divide => VirtualKeyCode::NumpadDivide,
|
||||
@@ -161,7 +161,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
|
||||
ffi::XK_Shift_R => VirtualKeyCode::RShift,
|
||||
ffi::XK_Control_L => VirtualKeyCode::LControl,
|
||||
ffi::XK_Control_R => VirtualKeyCode::RControl,
|
||||
//ffi::XK_Caps_Lock => VirtualKeyCode::Caps_lock,
|
||||
ffi::XK_Caps_Lock => VirtualKeyCode::Capital,
|
||||
//ffi::XK_Shift_Lock => VirtualKeyCode::Shift_lock,
|
||||
//ffi::XK_Meta_L => VirtualKeyCode::Meta_l,
|
||||
//ffi::XK_Meta_R => VirtualKeyCode::Meta_r,
|
||||
|
||||
@@ -62,7 +62,7 @@ pub unsafe fn set_destroy_callback(
|
||||
inner: &ImeInner,
|
||||
) -> Result<(), XError> {
|
||||
xim_set_callback(
|
||||
&xconn,
|
||||
xconn,
|
||||
im,
|
||||
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
||||
&inner.destroy_callback as *const _ as *mut _,
|
||||
@@ -70,6 +70,7 @@ pub unsafe fn set_destroy_callback(
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ReplaceImError {
|
||||
MethodOpenFailed(PotentialInputMethods),
|
||||
ContextCreationFailed(ImeContextCreationError),
|
||||
@@ -107,8 +108,19 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
let mut new_contexts = HashMap::new();
|
||||
for (window, old_context) in (*inner).contexts.iter() {
|
||||
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
|
||||
let is_allowed = old_context
|
||||
.as_ref()
|
||||
.map(|old_context| old_context.is_allowed)
|
||||
.unwrap_or_default();
|
||||
let new_context = {
|
||||
let result = ImeContext::new(xconn, new_im.im, *window, spot);
|
||||
let result = ImeContext::new(
|
||||
xconn,
|
||||
new_im.im,
|
||||
*window,
|
||||
spot,
|
||||
is_allowed,
|
||||
(*inner).event_sender.clone(),
|
||||
);
|
||||
if result.is_err() {
|
||||
let _ = close_im(xconn, new_im.im);
|
||||
}
|
||||
@@ -136,13 +148,17 @@ pub unsafe extern "C" fn xim_instantiate_callback(
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
let xconn = &(*inner).xconn;
|
||||
let result = replace_im(inner);
|
||||
if result.is_ok() {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
} else if result.is_err() && (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
result.expect("Failed to reopen input method");
|
||||
match replace_im(inner) {
|
||||
Ok(()) => {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
}
|
||||
Err(err) => {
|
||||
if (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to reopen input method: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,12 +179,12 @@ pub unsafe extern "C" fn xim_destroy_callback(
|
||||
if !(*inner).is_fallback {
|
||||
let _ = set_instantiate_callback(xconn, client_data);
|
||||
// Attempt to open fallback input method.
|
||||
let result = replace_im(inner);
|
||||
if result.is_ok() {
|
||||
(*inner).is_fallback = true;
|
||||
} else {
|
||||
// We have no usable input methods!
|
||||
result.expect("Failed to open fallback input method");
|
||||
match replace_im(inner) {
|
||||
Ok(()) => (*inner).is_fallback = true,
|
||||
Err(err) => {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to open fallback input method: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,201 @@
|
||||
use std::{
|
||||
os::raw::{c_short, c_void},
|
||||
ptr,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::mem::transmute;
|
||||
use std::os::raw::c_short;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
use std::ffi::CStr;
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
/// IME creation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ImeContextCreationError {
|
||||
/// Got the error from Xlib.
|
||||
XError(XError),
|
||||
|
||||
/// Got null pointer from Xlib but without exact reason.
|
||||
Null,
|
||||
}
|
||||
|
||||
unsafe fn create_pre_edit_attr<'a>(
|
||||
xconn: &'a Arc<XConnection>,
|
||||
ic_spot: &'a ffi::XPoint,
|
||||
) -> util::XSmartPointer<'a, c_void> {
|
||||
util::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr() as *const _,
|
||||
ic_spot,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL")
|
||||
/// The callback used by XIM preedit functions.
|
||||
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
|
||||
|
||||
/// Wrapper for creating XIM callbacks.
|
||||
#[inline]
|
||||
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
|
||||
XIMCallback {
|
||||
client_data,
|
||||
callback: Some(callback),
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: this struct doesn't destroy its XIC resource when dropped.
|
||||
/// The server started preedit.
|
||||
extern "C" fn preedit_start_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) -> i32 {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
client_data.text.clear();
|
||||
client_data.cursor_pos = 0;
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::Start))
|
||||
.expect("failed to send preedit start event");
|
||||
-1
|
||||
}
|
||||
|
||||
/// Done callback is used when the preedit should be hidden.
|
||||
extern "C" fn preedit_done_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
// Drop text buffer and reset cursor position on done.
|
||||
client_data.text = Vec::new();
|
||||
client_data.cursor_pos = 0;
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::End))
|
||||
.expect("failed to send preedit end event");
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter()
|
||||
.take(pos)
|
||||
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
|
||||
}
|
||||
|
||||
/// Preedit text information to be drawn inline by the client.
|
||||
extern "C" fn preedit_draw_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
|
||||
client_data.cursor_pos = call_data.caret as usize;
|
||||
|
||||
let chg_range =
|
||||
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
|
||||
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
|
||||
warn!(
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
|
||||
client_data.text.len(),
|
||||
call_data.chg_first,
|
||||
call_data.chg_length
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// NULL indicate text deletion
|
||||
let mut new_chars = if call_data.text.is_null() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let xim_text = unsafe { &mut *(call_data.text) };
|
||||
if xim_text.encoding_is_wchar > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { xim_text.string.multi_byte };
|
||||
|
||||
if new_text.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { CStr::from_ptr(new_text) };
|
||||
|
||||
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
|
||||
.chars()
|
||||
.collect()
|
||||
};
|
||||
let mut old_text_tail = client_data.text.split_off(chg_range.end);
|
||||
client_data.text.truncate(chg_range.start);
|
||||
client_data.text.append(&mut new_chars);
|
||||
client_data.text.append(&mut old_text_tail);
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
|
||||
/// Handling of cursor movements in preedit text.
|
||||
extern "C" fn preedit_caret_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
|
||||
|
||||
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
|
||||
client_data.cursor_pos = call_data.position as usize;
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to simplify callback creation and latter passing into Xlib XIM.
|
||||
struct PreeditCallbacks {
|
||||
start_callback: ffi::XIMCallback,
|
||||
done_callback: ffi::XIMCallback,
|
||||
draw_callback: ffi::XIMCallback,
|
||||
caret_callback: ffi::XIMCallback,
|
||||
}
|
||||
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
transmute(preedit_start_callback as usize)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
|
||||
|
||||
PreeditCallbacks {
|
||||
start_callback,
|
||||
done_callback,
|
||||
caret_callback,
|
||||
draw_callback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeContextClientData {
|
||||
window: ffi::Window,
|
||||
event_sender: ImeEventSender,
|
||||
text: Vec<char>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
// XXX: this struct doesn't destroy its XIC resource when dropped.
|
||||
// This is intentional, as it doesn't have enough information to know whether or not the context
|
||||
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
|
||||
// through `ImeInner`.
|
||||
#[derive(Debug)]
|
||||
pub struct ImeContext {
|
||||
pub ic: ffi::XIC,
|
||||
pub ic_spot: ffi::XPoint,
|
||||
pub(super) ic: ffi::XIC,
|
||||
pub(super) ic_spot: ffi::XPoint,
|
||||
pub(super) is_allowed: bool,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
|
||||
// there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
}
|
||||
|
||||
impl ImeContext {
|
||||
@@ -44,66 +204,111 @@ impl ImeContext {
|
||||
im: ffi::XIM,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
is_allowed: bool,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let ic = if let Some(ic_spot) = ic_spot {
|
||||
ImeContext::create_ic_with_spot(xconn, im, window, ic_spot)
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
event_sender,
|
||||
text: Vec::new(),
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let ic = if is_allowed {
|
||||
ImeContext::create_ic(xconn, im, window, client_data as ffi::XPointer)
|
||||
.ok_or(ImeContextCreationError::Null)?
|
||||
} else {
|
||||
ImeContext::create_ic(xconn, im, window)
|
||||
ImeContext::create_none_ic(xconn, im, window).ok_or(ImeContextCreationError::Null)?
|
||||
};
|
||||
|
||||
let ic = ic.ok_or(ImeContextCreationError::Null)?;
|
||||
xconn
|
||||
.check_errors()
|
||||
.map_err(ImeContextCreationError::XError)?;
|
||||
|
||||
Ok(ImeContext {
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }),
|
||||
})
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
is_allowed,
|
||||
_client_data: Box::from_raw(client_data),
|
||||
};
|
||||
|
||||
// Set the spot location, if it's present.
|
||||
if let Some(ic_spot) = ic_spot {
|
||||
context.set_spot(xconn, ic_spot.x, ic_spot.y)
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
unsafe fn create_none_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = (xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
ffi::XIMPreeditNone | ffi::XIMStatusNone,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
unsafe fn create_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
window: ffi::Window,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = (xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
if ic.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(ic)
|
||||
}
|
||||
}
|
||||
let preedit_callbacks = PreeditCallbacks::new(client_data);
|
||||
let preedit_attr = util::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.start_callback) as *const _,
|
||||
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.done_callback) as *const _,
|
||||
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.caret_callback) as *const _,
|
||||
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.draw_callback) as *const _,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
unsafe fn create_ic_with_spot(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
window: ffi::Window,
|
||||
ic_spot: ffi::XPoint,
|
||||
) -> Option<ffi::XIC> {
|
||||
let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot);
|
||||
let ic = (xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
pre_edit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
if ic.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(ic)
|
||||
}
|
||||
let ic = {
|
||||
let ic = (xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
|
||||
// If we've failed to create IC with preedit callbacks fallback to normal one.
|
||||
if ic.is_null() {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
} else {
|
||||
ic
|
||||
}
|
||||
};
|
||||
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
@@ -120,18 +325,34 @@ impl ImeContext {
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
||||
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
|
||||
// window and couldn't be changed.
|
||||
//
|
||||
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
|
||||
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
||||
if self.ic_spot.x == x && self.ic_spot.y == y {
|
||||
if !self.is_allowed || self.ic_spot.x == x && self.ic_spot.y == y {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ic_spot = ffi::XPoint { x, y };
|
||||
|
||||
unsafe {
|
||||
let pre_edit_attr = create_pre_edit_attr(xconn, &self.ic_spot);
|
||||
let preedit_attr = util::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr(),
|
||||
&self.ic_spot,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
(xconn.xlib.XSetICValues)(
|
||||
self.ic,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
pre_edit_attr.ptr,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::{collections::HashMap, mem, ptr, sync::Arc};
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{context::ImeContext, input_method::PotentialInputMethods};
|
||||
use crate::platform_impl::platform::x11::ime::ImeEventSender;
|
||||
|
||||
pub unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||
(xconn.xlib.XCloseIM)(im);
|
||||
@@ -22,6 +23,7 @@ pub struct ImeInner {
|
||||
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
|
||||
// WARNING: this is initially zeroed!
|
||||
pub destroy_callback: ffi::XIMCallback,
|
||||
pub event_sender: ImeEventSender,
|
||||
// Indicates whether or not the the input method was destroyed on the server end
|
||||
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
|
||||
pub is_destroyed: bool,
|
||||
@@ -29,13 +31,18 @@ pub struct ImeInner {
|
||||
}
|
||||
|
||||
impl ImeInner {
|
||||
pub fn new(xconn: Arc<XConnection>, potential_input_methods: PotentialInputMethods) -> Self {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
potential_input_methods: PotentialInputMethods,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Self {
|
||||
ImeInner {
|
||||
xconn,
|
||||
im: ptr::null_mut(),
|
||||
potential_input_methods,
|
||||
contexts: HashMap::new(),
|
||||
destroy_callback: unsafe { mem::zeroed() },
|
||||
event_sender,
|
||||
is_destroyed: false,
|
||||
is_fallback: false,
|
||||
}
|
||||
@@ -58,10 +65,8 @@ impl ImeInner {
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
|
||||
for context in self.contexts.values() {
|
||||
if let &Some(ref context) = context {
|
||||
self.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
for context in self.contexts.values().flatten() {
|
||||
self.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
Ok(!self.is_destroyed)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,12 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
lazy_static! {
|
||||
static ref GLOBAL_LOCK: Mutex<()> = Default::default();
|
||||
}
|
||||
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
|
||||
|
||||
unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
|
||||
let _lock = GLOBAL_LOCK.lock();
|
||||
@@ -42,12 +41,12 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
|
||||
#[derive(Debug)]
|
||||
pub struct InputMethod {
|
||||
pub im: ffi::XIM,
|
||||
name: String,
|
||||
_name: String,
|
||||
}
|
||||
|
||||
impl InputMethod {
|
||||
fn new(im: ffi::XIM, name: String) -> Self {
|
||||
InputMethod { im, name }
|
||||
InputMethod { im, _name: name }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +62,7 @@ pub enum InputMethodResult {
|
||||
|
||||
impl InputMethodResult {
|
||||
pub fn is_fallback(&self) -> bool {
|
||||
if let &InputMethodResult::Fallback(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(self, InputMethodResult::Fallback(_))
|
||||
}
|
||||
|
||||
pub fn ok(self) -> Option<InputMethod> {
|
||||
@@ -185,10 +180,10 @@ impl PotentialInputMethod {
|
||||
}
|
||||
|
||||
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
|
||||
// came from, and if it succceeded.
|
||||
// came from, and if it succeeded.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PotentialInputMethods {
|
||||
// On correctly configured systems, the XMODIFIERS environemnt variable tells us everything we
|
||||
// On correctly configured systems, the XMODIFIERS environment variable tells us everything we
|
||||
// need to know.
|
||||
xmodifiers: Option<PotentialInputMethod>,
|
||||
// We have some standard options at our disposal that should ostensibly always work. For users
|
||||
@@ -218,7 +213,7 @@ impl PotentialInputMethods {
|
||||
// that case, we get `None` and end up skipping ahead to the next method.
|
||||
xmodifiers,
|
||||
fallbacks: [
|
||||
// This is a standard input method that supports compose equences, which should
|
||||
// This is a standard input method that supports compose sequences, which should
|
||||
// always be available. `@im=none` appears to mean the same thing.
|
||||
PotentialInputMethod::from_str("@im=local"),
|
||||
// This explicitly specifies to use the implementation-dependent default, though
|
||||
@@ -249,7 +244,7 @@ impl PotentialInputMethods {
|
||||
pub fn open_im(
|
||||
&mut self,
|
||||
xconn: &Arc<XConnection>,
|
||||
callback: Option<&dyn Fn() -> ()>,
|
||||
callback: Option<&dyn Fn()>,
|
||||
) -> InputMethodResult {
|
||||
use self::InputMethodResult::*;
|
||||
|
||||
@@ -259,10 +254,8 @@ impl PotentialInputMethods {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return XModifiers(im);
|
||||
} else {
|
||||
if let Some(ref callback) = callback {
|
||||
callback();
|
||||
}
|
||||
} else if let Some(ref callback) = callback {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,29 @@ use self::{
|
||||
inner::{close_im, ImeInner},
|
||||
input_method::PotentialInputMethods,
|
||||
};
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ImeEvent {
|
||||
Enabled,
|
||||
Start,
|
||||
Update(String, usize),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>;
|
||||
pub type ImeSender = Sender<(ffi::Window, i16, i16)>;
|
||||
pub type ImeReceiver = Receiver<ImeRequest>;
|
||||
pub type ImeSender = Sender<ImeRequest>;
|
||||
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
|
||||
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
|
||||
|
||||
/// Request to control XIM handler from the window.
|
||||
pub enum ImeRequest {
|
||||
/// Set IME spot position for given `window_id`.
|
||||
Position(ffi::Window, i16, i16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(ffi::Window, bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImeCreationError {
|
||||
@@ -37,11 +57,14 @@ pub struct Ime {
|
||||
}
|
||||
|
||||
impl Ime {
|
||||
pub fn new(xconn: Arc<XConnection>) -> Result<Self, ImeCreationError> {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeCreationError> {
|
||||
let potential_input_methods = PotentialInputMethods::new(&xconn);
|
||||
|
||||
let (mut inner, client_data) = {
|
||||
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods));
|
||||
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
|
||||
let inner_ptr = Box::into_raw(inner);
|
||||
let client_data = inner_ptr as _;
|
||||
let destroy_callback = ffi::XIMCallback {
|
||||
@@ -88,12 +111,37 @@ impl Ime {
|
||||
// Ok(_) indicates that nothing went wrong internally
|
||||
// Ok(true) indicates that the action was actually performed
|
||||
// Ok(false) indicates that the action is not presently applicable
|
||||
pub fn create_context(&mut self, window: ffi::Window) -> Result<bool, ImeContextCreationError> {
|
||||
pub fn create_context(
|
||||
&mut self,
|
||||
window: ffi::Window,
|
||||
with_preedit: bool,
|
||||
) -> Result<bool, ImeContextCreationError> {
|
||||
let context = if self.is_destroyed() {
|
||||
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { ImeContext::new(&self.inner.xconn, self.inner.im, window, None) }?)
|
||||
let event = if with_preedit {
|
||||
ImeEvent::Enabled
|
||||
} else {
|
||||
// There's no IME without preedit.
|
||||
ImeEvent::Disabled
|
||||
};
|
||||
|
||||
self.inner
|
||||
.event_sender
|
||||
.send((window, event))
|
||||
.expect("Failed to send enabled event");
|
||||
|
||||
Some(unsafe {
|
||||
ImeContext::new(
|
||||
&self.inner.xconn,
|
||||
self.inner.im,
|
||||
window,
|
||||
None,
|
||||
with_preedit,
|
||||
self.inner.event_sender.clone(),
|
||||
)
|
||||
}?)
|
||||
};
|
||||
self.inner.contexts.insert(window, context);
|
||||
Ok(!self.is_destroyed())
|
||||
@@ -151,6 +199,24 @@ impl Ime {
|
||||
context.set_spot(&self.xconn, x as _, y as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
if allowed == context.is_allowed {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove context for that window.
|
||||
let _ = self.remove_context(window);
|
||||
|
||||
// Create new context supporting IME input.
|
||||
let _ = self.create_context(window, allowed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ime {
|
||||
|
||||
@@ -23,7 +23,7 @@ pub use self::{
|
||||
};
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cell::{Cell, RefCell},
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::CStr,
|
||||
mem::{self, MaybeUninit},
|
||||
@@ -32,7 +32,7 @@ use std::{
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice,
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
sync::mpsc::{Receiver, Sender, TryRecvError},
|
||||
sync::{mpsc, Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -40,18 +40,24 @@ use std::{
|
||||
use libc::{self, setlocale, LC_CTYPE};
|
||||
|
||||
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
|
||||
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
|
||||
|
||||
use self::{
|
||||
dnd::{Dnd, DndState},
|
||||
event_processor::EventProcessor,
|
||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
|
||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
|
||||
util::modifiers::ModifierKeymap,
|
||||
};
|
||||
use crate::{
|
||||
error::OsError as RootOsError,
|
||||
event::{Event, StartCause},
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
|
||||
event_loop::{
|
||||
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
||||
},
|
||||
platform_impl::{
|
||||
platform::{sticky_exit_callback, WindowId},
|
||||
PlatformSpecificWindowBuilderAttributes,
|
||||
},
|
||||
window::WindowAttributes,
|
||||
};
|
||||
|
||||
@@ -63,6 +69,40 @@ struct WakeSender<T> {
|
||||
waker: Arc<Waker>,
|
||||
}
|
||||
|
||||
struct PeekableReceiver<T> {
|
||||
recv: Receiver<T>,
|
||||
first: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> PeekableReceiver<T> {
|
||||
pub fn from_recv(recv: Receiver<T>) -> Self {
|
||||
Self { recv, first: None }
|
||||
}
|
||||
pub fn has_incoming(&mut self) -> bool {
|
||||
if self.first.is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self.recv.try_recv() {
|
||||
Ok(v) => {
|
||||
self.first = Some(v);
|
||||
true
|
||||
}
|
||||
Err(TryRecvError::Empty) => false,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
warn!("Channel was disconnected when checking incoming");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
|
||||
if let Some(first) = self.first.take() {
|
||||
return Ok(first);
|
||||
}
|
||||
self.recv.try_recv()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
xconn: Arc<XConnection>,
|
||||
wm_delete_window: ffi::Atom,
|
||||
@@ -72,6 +112,7 @@ pub struct EventLoopWindowTarget<T> {
|
||||
ime: RefCell<Ime>,
|
||||
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
device_event_filter: Cell<DeviceEventFilter>,
|
||||
_marker: ::std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -79,8 +120,8 @@ pub struct EventLoop<T: 'static> {
|
||||
poll: Poll,
|
||||
waker: Arc<Waker>,
|
||||
event_processor: EventProcessor<T>,
|
||||
redraw_channel: Receiver<WindowId>,
|
||||
user_channel: Receiver<T>, //waker.wake needs to be called whenever something gets sent
|
||||
redraw_receiver: PeekableReceiver<WindowId>,
|
||||
user_receiver: PeekableReceiver<T>, //waker.wake needs to be called whenever something gets sent
|
||||
user_sender: Sender<T>,
|
||||
target: Rc<RootELW<T>>,
|
||||
}
|
||||
@@ -111,6 +152,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
||||
|
||||
let (ime_sender, ime_receiver) = mpsc::channel();
|
||||
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
|
||||
// Input methods will open successfully without setting the locale, but it won't be
|
||||
// possible to actually commit pre-edit sequences.
|
||||
unsafe {
|
||||
@@ -135,7 +177,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
let ime = RefCell::new({
|
||||
let result = Ime::new(Arc::clone(&xconn));
|
||||
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
|
||||
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
||||
panic!("Failed to open input method: {:#?}", state);
|
||||
}
|
||||
@@ -195,21 +237,27 @@ impl<T: 'static> EventLoop<T> {
|
||||
let (user_sender, user_channel) = std::sync::mpsc::channel();
|
||||
let (redraw_sender, redraw_channel) = std::sync::mpsc::channel();
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
ime,
|
||||
root,
|
||||
windows: Default::default(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
ime_sender,
|
||||
xconn,
|
||||
wm_delete_window,
|
||||
net_wm_ping,
|
||||
redraw_sender: WakeSender {
|
||||
sender: redraw_sender, // not used again so no clone
|
||||
waker: waker.clone(),
|
||||
},
|
||||
device_event_filter: Default::default(),
|
||||
};
|
||||
|
||||
// Set initial device event filter.
|
||||
window_target.update_device_event_filter(true);
|
||||
|
||||
let target = Rc::new(RootELW {
|
||||
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
||||
ime,
|
||||
root,
|
||||
windows: Default::default(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
ime_sender,
|
||||
xconn,
|
||||
wm_delete_window,
|
||||
net_wm_ping,
|
||||
redraw_sender: WakeSender {
|
||||
sender: redraw_sender, // not used again so no clone
|
||||
waker: waker.clone(),
|
||||
},
|
||||
}),
|
||||
p: super::EventLoopWindowTarget::X(window_target),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
});
|
||||
|
||||
@@ -219,12 +267,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
devices: Default::default(),
|
||||
randr_event_offset,
|
||||
ime_receiver,
|
||||
ime_event_receiver,
|
||||
xi2ext,
|
||||
mod_keymap,
|
||||
device_mod_state: Default::default(),
|
||||
num_touch: 0,
|
||||
first_touch: None,
|
||||
active_window: None,
|
||||
is_composing: false,
|
||||
};
|
||||
|
||||
// Register for device hotplug events
|
||||
@@ -240,8 +290,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
poll,
|
||||
waker,
|
||||
event_processor,
|
||||
redraw_channel,
|
||||
user_channel,
|
||||
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
|
||||
user_receiver: PeekableReceiver::from_recv(user_channel),
|
||||
user_sender,
|
||||
target,
|
||||
}
|
||||
@@ -258,33 +308,53 @@ impl<T: 'static> EventLoop<T> {
|
||||
&self.target
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, mut callback: F)
|
||||
pub fn run_return<F>(&mut self, mut callback: F) -> i32
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
let mut control_flow = ControlFlow::default();
|
||||
let mut events = Events::with_capacity(8);
|
||||
let mut cause = StartCause::Init;
|
||||
|
||||
loop {
|
||||
struct IterationResult {
|
||||
deadline: Option<Instant>,
|
||||
timeout: Option<Duration>,
|
||||
wait_start: Instant,
|
||||
}
|
||||
fn single_iteration<T, F>(
|
||||
this: &mut EventLoop<T>,
|
||||
control_flow: &mut ControlFlow,
|
||||
cause: &mut StartCause,
|
||||
callback: &mut F,
|
||||
) -> IterationResult
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::NewEvents(cause),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
crate::event::Event::NewEvents(*cause),
|
||||
&this.target,
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though X11
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
if *cause == StartCause::Init {
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::Resumed,
|
||||
&this.target,
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
// Process all pending events
|
||||
self.drain_events(&mut callback, &mut control_flow);
|
||||
this.drain_events(callback, control_flow);
|
||||
|
||||
// Empty the user event buffer
|
||||
{
|
||||
while let Ok(event) = self.user_channel.try_recv() {
|
||||
while let Ok(event) = this.user_receiver.try_recv() {
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::UserEvent(event),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
&this.target,
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -292,26 +362,26 @@ impl<T: 'static> EventLoop<T> {
|
||||
{
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::MainEventsCleared,
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
&this.target,
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
// Empty the redraw requests
|
||||
{
|
||||
let mut windows = HashSet::new();
|
||||
|
||||
while let Ok(window_id) = self.redraw_channel.try_recv() {
|
||||
while let Ok(window_id) = this.redraw_receiver.try_recv() {
|
||||
windows.insert(window_id);
|
||||
}
|
||||
|
||||
for window_id in windows {
|
||||
let window_id = crate::window::WindowId(super::WindowId::X(window_id));
|
||||
let window_id = crate::window::WindowId(window_id);
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(window_id),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
&this.target,
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -319,9 +389,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
{
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::RedrawEventsCleared,
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
&this.target,
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -329,14 +399,20 @@ impl<T: 'static> EventLoop<T> {
|
||||
let (deadline, timeout);
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break,
|
||||
ControlFlow::ExitWithCode(_) => {
|
||||
return IterationResult {
|
||||
wait_start: start,
|
||||
deadline: None,
|
||||
timeout: None,
|
||||
};
|
||||
}
|
||||
ControlFlow::Poll => {
|
||||
cause = StartCause::Poll;
|
||||
*cause = StartCause::Poll;
|
||||
deadline = None;
|
||||
timeout = Some(Duration::from_millis(0));
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
cause = StartCause::WaitCancelled {
|
||||
*cause = StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
};
|
||||
@@ -344,53 +420,88 @@ impl<T: 'static> EventLoop<T> {
|
||||
timeout = None;
|
||||
}
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
cause = StartCause::ResumeTimeReached {
|
||||
*cause = StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume: wait_deadline,
|
||||
requested_resume: *wait_deadline,
|
||||
};
|
||||
timeout = if wait_deadline > start {
|
||||
Some(wait_deadline - start)
|
||||
timeout = if *wait_deadline > start {
|
||||
Some(*wait_deadline - start)
|
||||
} else {
|
||||
Some(Duration::from_millis(0))
|
||||
};
|
||||
deadline = Some(wait_deadline);
|
||||
deadline = Some(*wait_deadline);
|
||||
}
|
||||
}
|
||||
|
||||
// If the XConnection already contains buffered events, we don't
|
||||
// need to wait for data on the socket.
|
||||
if !self.event_processor.poll() {
|
||||
if let Err(e) = self.poll.poll(&mut events, timeout) {
|
||||
IterationResult {
|
||||
wait_start: start,
|
||||
deadline,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
let mut control_flow = ControlFlow::default();
|
||||
let mut events = Events::with_capacity(8);
|
||||
let mut cause = StartCause::Init;
|
||||
|
||||
// run the initial loop iteration
|
||||
let mut iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback);
|
||||
|
||||
let exit_code = loop {
|
||||
if let ControlFlow::ExitWithCode(code) = control_flow {
|
||||
break code;
|
||||
}
|
||||
let has_pending = self.event_processor.poll()
|
||||
|| self.user_receiver.has_incoming()
|
||||
|| self.redraw_receiver.has_incoming();
|
||||
if !has_pending {
|
||||
// Wait until
|
||||
if let Err(e) = self.poll.poll(&mut events, iter_result.timeout) {
|
||||
if e.raw_os_error() != Some(libc::EINTR) {
|
||||
panic!("epoll returned an error: {:?}", e);
|
||||
}
|
||||
}
|
||||
events.clear();
|
||||
|
||||
if control_flow == ControlFlow::Wait {
|
||||
// We don't go straight into executing the event loop iteration, we instead go
|
||||
// to the start of this loop and check again if there's any pending event. We
|
||||
// must do this because during the execution of the iteration we sometimes wake
|
||||
// the mio waker, and if the waker is already awaken before we call poll(),
|
||||
// then poll doesn't block, but it returns immediately. This caused the event
|
||||
// loop to run continuously even if the control_flow was `Wait`
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
|
||||
let wait_cancelled = iter_result
|
||||
.deadline
|
||||
.map_or(false, |deadline| Instant::now() < deadline);
|
||||
|
||||
if wait_cancelled {
|
||||
cause = StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: deadline,
|
||||
start: iter_result.wait_start,
|
||||
requested_resume: iter_result.deadline,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback);
|
||||
};
|
||||
|
||||
callback(
|
||||
crate::event::Event::LoopDestroyed,
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
);
|
||||
exit_code
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, callback: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
self.run_return(callback);
|
||||
::std::process::exit(0);
|
||||
let exit_code = self.run_return(callback);
|
||||
::std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow)
|
||||
@@ -409,10 +520,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
target,
|
||||
control_flow,
|
||||
&mut |event, window_target, control_flow| {
|
||||
if let Event::RedrawRequested(crate::window::WindowId(
|
||||
super::WindowId::X(wid),
|
||||
)) = event
|
||||
{
|
||||
if let Event::RedrawRequested(crate::window::WindowId(wid)) = event {
|
||||
wt.redraw_sender.sender.send(wid).unwrap();
|
||||
wt.redraw_sender.waker.wake().unwrap();
|
||||
} else {
|
||||
@@ -439,6 +547,37 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn x_connection(&self) -> &Arc<XConnection> {
|
||||
&self.xconn
|
||||
}
|
||||
|
||||
pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {
|
||||
self.device_event_filter.set(filter);
|
||||
}
|
||||
|
||||
/// Update the device event filter based on window focus.
|
||||
pub fn update_device_event_filter(&self, focus: bool) {
|
||||
let filter_events = self.device_event_filter.get() == DeviceEventFilter::Never
|
||||
|| (self.device_event_filter.get() == DeviceEventFilter::Unfocused && !focus);
|
||||
|
||||
let mut mask = 0;
|
||||
if !filter_events {
|
||||
mask = ffi::XI_RawMotionMask
|
||||
| ffi::XI_RawButtonPressMask
|
||||
| ffi::XI_RawButtonReleaseMask
|
||||
| ffi::XI_RawKeyPressMask
|
||||
| ffi::XI_RawKeyReleaseMask;
|
||||
}
|
||||
|
||||
self.xconn
|
||||
.select_xinput_events(self.root, ffi::XIAllMasterDevices, mask)
|
||||
.queue();
|
||||
}
|
||||
|
||||
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
|
||||
let mut display_handle = XlibDisplayHandle::empty();
|
||||
display_handle.display = self.xconn.display as *mut _;
|
||||
display_handle.screen =
|
||||
unsafe { (self.xconn.xlib.XDefaultScreen)(self.xconn.display as *mut _) };
|
||||
RawDisplayHandle::Xlib(display_handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
@@ -490,15 +629,6 @@ impl<'a> Deref for DeviceInfo<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(ffi::Window);
|
||||
|
||||
impl WindowId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(c_int);
|
||||
|
||||
@@ -519,7 +649,7 @@ impl Deref for Window {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
pub(crate) fn new<T>(
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
attribs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
@@ -538,7 +668,7 @@ impl Drop for Window {
|
||||
let window = self.deref();
|
||||
let xconn = &window.xconn;
|
||||
unsafe {
|
||||
(xconn.xlib.XDestroyWindow)(xconn.display, window.id().0);
|
||||
(xconn.xlib.XDestroyWindow)(xconn.display, window.id().0 as ffi::Window);
|
||||
// If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about.
|
||||
let _ = xconn.check_errors();
|
||||
}
|
||||
@@ -553,10 +683,7 @@ struct GenericEventCookie<'a> {
|
||||
}
|
||||
|
||||
impl<'a> GenericEventCookie<'a> {
|
||||
fn from_event<'b>(
|
||||
xconn: &'b XConnection,
|
||||
event: ffi::XEvent,
|
||||
) -> Option<GenericEventCookie<'b>> {
|
||||
fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'_>> {
|
||||
unsafe {
|
||||
let mut cookie: ffi::XGenericEventCookie = From::from(event);
|
||||
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
|
||||
@@ -584,7 +711,7 @@ struct XExtension {
|
||||
}
|
||||
|
||||
fn mkwid(w: ffi::Window) -> crate::window::WindowId {
|
||||
crate::window::WindowId(crate::platform_impl::WindowId::X(WindowId(w)))
|
||||
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as u64))
|
||||
}
|
||||
fn mkdid(w: c_int) -> crate::event::DeviceId {
|
||||
crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w)))
|
||||
@@ -592,7 +719,7 @@ fn mkdid(w: c_int) -> crate::event::DeviceId {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Device {
|
||||
name: String,
|
||||
_name: String,
|
||||
scroll_axes: Vec<(i32, ScrollAxis)>,
|
||||
// For master devices, this is the paired device (pointer <-> keyboard).
|
||||
// For slave devices, this is the master.
|
||||
@@ -613,52 +740,36 @@ enum ScrollOrientation {
|
||||
}
|
||||
|
||||
impl Device {
|
||||
fn new<T: 'static>(el: &EventProcessor<T>, info: &ffi::XIDeviceInfo) -> Self {
|
||||
fn new(info: &ffi::XIDeviceInfo) -> Self {
|
||||
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
|
||||
let mut scroll_axes = Vec::new();
|
||||
|
||||
let wt = get_xtarget(&el.target);
|
||||
|
||||
if Device::physical_device(info) {
|
||||
// Register for global raw events
|
||||
let mask = ffi::XI_RawMotionMask
|
||||
| ffi::XI_RawButtonPressMask
|
||||
| ffi::XI_RawButtonReleaseMask
|
||||
| ffi::XI_RawKeyPressMask
|
||||
| ffi::XI_RawKeyReleaseMask;
|
||||
// The request buffer is flushed when we poll for events
|
||||
wt.xconn
|
||||
.select_xinput_events(wt.root, info.deviceid, mask)
|
||||
.queue();
|
||||
|
||||
// Identify scroll axes
|
||||
for class_ptr in Device::classes(info) {
|
||||
let class = unsafe { &**class_ptr };
|
||||
match class._type {
|
||||
ffi::XIScrollClass => {
|
||||
let info = unsafe {
|
||||
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
|
||||
};
|
||||
scroll_axes.push((
|
||||
info.number,
|
||||
ScrollAxis {
|
||||
increment: info.increment,
|
||||
orientation: match info.scroll_type {
|
||||
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
|
||||
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
position: 0.0,
|
||||
if class._type == ffi::XIScrollClass {
|
||||
let info = unsafe {
|
||||
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
|
||||
};
|
||||
scroll_axes.push((
|
||||
info.number,
|
||||
ScrollAxis {
|
||||
increment: info.increment,
|
||||
orientation: match info.scroll_type {
|
||||
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
|
||||
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
position: 0.0,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut device = Device {
|
||||
name: name.into_owned(),
|
||||
_name: name.into_owned(),
|
||||
scroll_axes,
|
||||
attachment: info.attachment,
|
||||
};
|
||||
@@ -670,20 +781,17 @@ impl Device {
|
||||
if Device::physical_device(info) {
|
||||
for class_ptr in Device::classes(info) {
|
||||
let class = unsafe { &**class_ptr };
|
||||
match class._type {
|
||||
ffi::XIValuatorClass => {
|
||||
let info = unsafe {
|
||||
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
|
||||
};
|
||||
if let Some(&mut (_, ref mut axis)) = self
|
||||
.scroll_axes
|
||||
.iter_mut()
|
||||
.find(|&&mut (axis, _)| axis == info.number)
|
||||
{
|
||||
axis.position = info.value;
|
||||
}
|
||||
if class._type == ffi::XIValuatorClass {
|
||||
let info = unsafe {
|
||||
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
|
||||
};
|
||||
if let Some(&mut (_, ref mut axis)) = self
|
||||
.scroll_axes
|
||||
.iter_mut()
|
||||
.find(|&&mut (axis, _)| axis == info.number)
|
||||
{
|
||||
axis.position = info.value;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::os::raw::*;
|
||||
use std::slice;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::{
|
||||
ffi::{
|
||||
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
|
||||
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
|
||||
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources,
|
||||
},
|
||||
util, XConnection, XError,
|
||||
};
|
||||
@@ -18,9 +20,7 @@ use crate::{
|
||||
// Used for testing. This should always be committed as false.
|
||||
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
||||
|
||||
lazy_static! {
|
||||
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
|
||||
}
|
||||
static MONITORS: Lazy<Mutex<Option<Vec<MonitorHandle>>>> = Lazy::new(Mutex::default);
|
||||
|
||||
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
|
||||
// We update this lazily.
|
||||
@@ -31,7 +31,7 @@ pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
pub(crate) refresh_rate_millihertz: u32,
|
||||
pub(crate) native_mode: RRMode,
|
||||
pub(crate) monitor: Option<MonitorHandle>,
|
||||
}
|
||||
@@ -48,8 +48,8 @@ impl VideoMode {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -72,6 +72,8 @@ pub struct MonitorHandle {
|
||||
position: (i32, i32),
|
||||
/// If the monitor is the primary one
|
||||
primary: bool,
|
||||
/// The refresh rate used by monitor.
|
||||
refresh_rate_millihertz: Option<u32>,
|
||||
/// The DPI scale factor
|
||||
pub(crate) scale_factor: f64,
|
||||
/// Used to determine which windows are on this monitor
|
||||
@@ -90,7 +92,7 @@ impl Eq for MonitorHandle {}
|
||||
|
||||
impl PartialOrd for MonitorHandle {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(&other))
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +108,15 @@ impl std::hash::Hash for MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> {
|
||||
if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 {
|
||||
Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
fn new(
|
||||
xconn: &XConnection,
|
||||
@@ -117,10 +128,22 @@ impl MonitorHandle {
|
||||
let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
|
||||
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
|
||||
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
|
||||
|
||||
// Get the refresh rate of the current video mode.
|
||||
let current_mode = unsafe { (*crtc).mode };
|
||||
let screen_modes =
|
||||
unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) };
|
||||
let refresh_rate_millihertz = screen_modes
|
||||
.iter()
|
||||
.find(|mode| mode.id == current_mode)
|
||||
.and_then(mode_refresh_rate_millihertz);
|
||||
|
||||
let rect = util::AaRect::new(position, dimensions);
|
||||
|
||||
Some(MonitorHandle {
|
||||
id,
|
||||
name,
|
||||
refresh_rate_millihertz,
|
||||
scale_factor,
|
||||
dimensions,
|
||||
position,
|
||||
@@ -137,6 +160,7 @@ impl MonitorHandle {
|
||||
scale_factor: 1.0,
|
||||
dimensions: (1, 1),
|
||||
position: (0, 0),
|
||||
refresh_rate_millihertz: None,
|
||||
primary: true,
|
||||
rect: util::AaRect::new((0, 0), (1, 1)),
|
||||
video_modes: Vec::new(),
|
||||
@@ -165,6 +189,10 @@ impl MonitorHandle {
|
||||
self.position.into()
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
self.refresh_rate_millihertz
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.scale_factor
|
||||
@@ -204,7 +232,7 @@ impl XConnection {
|
||||
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
|
||||
if overlapping_area > largest_overlap {
|
||||
largest_overlap = overlapping_area;
|
||||
matched_monitor = &monitor;
|
||||
matched_monitor = monitor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,11 +258,11 @@ impl XConnection {
|
||||
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
|
||||
}
|
||||
|
||||
let mut available;
|
||||
let mut has_primary = false;
|
||||
|
||||
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
||||
available = Vec::with_capacity((*resources).ncrtc as usize);
|
||||
let mut available = Vec::with_capacity((*resources).ncrtc as usize);
|
||||
|
||||
for crtc_index in 0..(*resources).ncrtc {
|
||||
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
|
||||
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||
@@ -242,8 +270,11 @@ impl XConnection {
|
||||
if is_active {
|
||||
let is_primary = *(*crtc).outputs.offset(0) == primary;
|
||||
has_primary |= is_primary;
|
||||
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
|
||||
.map(|monitor_id| available.push(monitor_id));
|
||||
if let Some(monitor_id) =
|
||||
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
|
||||
{
|
||||
available.push(monitor_id)
|
||||
}
|
||||
}
|
||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||
}
|
||||
|
||||
@@ -5,15 +5,14 @@ use std::{
|
||||
os::raw::*,
|
||||
};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::*;
|
||||
|
||||
type AtomCache = HashMap<CString, ffi::Atom>;
|
||||
|
||||
lazy_static! {
|
||||
static ref ATOM_CACHE: Mutex<AtomCache> = Mutex::new(HashMap::with_capacity(2048));
|
||||
}
|
||||
static ATOM_CACHE: Lazy<Mutex<AtomCache>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(2048)));
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_atom<T: AsRef<CStr> + Debug>(&self, name: T) -> ffi::Atom {
|
||||
|
||||
@@ -23,9 +23,9 @@ impl Format {
|
||||
|
||||
pub fn get_actual_size(&self) -> usize {
|
||||
match self {
|
||||
&Format::Char => mem::size_of::<c_char>(),
|
||||
&Format::Short => mem::size_of::<c_short>(),
|
||||
&Format::Long => mem::size_of::<c_long>(),
|
||||
Format::Char => mem::size_of::<c_char>(),
|
||||
Format::Short => mem::size_of::<c_short>(),
|
||||
Format::Long => mem::size_of::<c_long>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ pub struct LogicalFrameExtents {
|
||||
pub bottom: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FrameExtentsHeuristicPath {
|
||||
Supported,
|
||||
UnsupportedNested,
|
||||
@@ -248,7 +248,7 @@ impl XConnection {
|
||||
);
|
||||
|
||||
// The list of children isn't used
|
||||
if children != ptr::null_mut() {
|
||||
if !children.is_null() {
|
||||
(self.xlib.XFree)(children as *mut _);
|
||||
}
|
||||
|
||||
@@ -370,8 +370,12 @@ impl XConnection {
|
||||
let top = offset_y;
|
||||
let bottom = diff_y.saturating_sub(offset_y);
|
||||
|
||||
let frame_extents =
|
||||
FrameExtents::new(left.into(), right.into(), top.into(), bottom.into());
|
||||
let frame_extents = FrameExtents::new(
|
||||
left as c_ulong,
|
||||
right as c_ulong,
|
||||
top as c_ulong,
|
||||
bottom as c_ulong,
|
||||
);
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: UnsupportedNested,
|
||||
@@ -379,7 +383,7 @@ impl XConnection {
|
||||
} else {
|
||||
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
|
||||
// border value. This is convenient, since we can use it to get an accurate frame.
|
||||
let frame_extents = FrameExtents::from_border(border.into());
|
||||
let frame_extents = FrameExtents::from_border(border as c_ulong);
|
||||
FrameExtentsHeuristic {
|
||||
frame_extents,
|
||||
heuristic_path: UnsupportedBordered,
|
||||
|
||||
@@ -173,6 +173,12 @@ impl MotifHints {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MotifHints {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MwmHints {
|
||||
fn as_slice(&self) -> &[c_ulong] {
|
||||
unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) }
|
||||
@@ -317,7 +323,7 @@ impl XConnection {
|
||||
let mut hints = MotifHints::new();
|
||||
|
||||
if let Ok(props) = self.get_property::<c_ulong>(window, motif_hints, motif_hints) {
|
||||
hints.hints.flags = props.get(0).cloned().unwrap_or(0);
|
||||
hints.hints.flags = props.first().cloned().unwrap_or(0);
|
||||
hints.hints.functions = props.get(1).cloned().unwrap_or(0);
|
||||
hints.hints.decorations = props.get(2).cloned().unwrap_or(0);
|
||||
hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::assertions_on_constants)]
|
||||
|
||||
use super::*;
|
||||
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
|
||||
|
||||
@@ -27,7 +29,7 @@ impl Icon {
|
||||
data.push(rgba_icon.height as Cardinal);
|
||||
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
|
||||
for pixel_index in 0..pixel_count {
|
||||
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
|
||||
let pixel = unsafe { &*pixels.add(pixel_index) };
|
||||
data.push(pixel.to_packed_argb());
|
||||
}
|
||||
data
|
||||
|
||||
@@ -36,7 +36,7 @@ impl Iterator for KeymapIter<'_> {
|
||||
|
||||
fn next(&mut self) -> Option<ffi::KeyCode> {
|
||||
if self.item.is_none() {
|
||||
while let Some((index, &item)) = self.iter.next() {
|
||||
for (index, &item) in self.iter.by_ref() {
|
||||
if item != 0 {
|
||||
self.index = index;
|
||||
self.item = Some(item);
|
||||
|
||||
@@ -151,7 +151,7 @@ impl ModifierKeyState {
|
||||
|
||||
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
|
||||
if let Some(modifier) = self.keys.remove(&keycode) {
|
||||
if self.keys.values().find(|&&m| m == modifier).is_none() {
|
||||
if !self.keys.values().any(|&m| m == modifier) {
|
||||
set_modifier(&mut self.state, modifier, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::{
|
||||
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
|
||||
*,
|
||||
};
|
||||
use crate::platform_impl::platform::x11::monitor;
|
||||
use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode};
|
||||
|
||||
/// Represents values of `WINIT_HIDPI_FACTOR`.
|
||||
@@ -39,15 +40,15 @@ impl XConnection {
|
||||
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
||||
(self.xlib.XrmInitialize)();
|
||||
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
|
||||
if resource_manager_str == ptr::null_mut() {
|
||||
if resource_manager_str.is_null() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
|
||||
let name: &str = "Xft.dpi:\t";
|
||||
for pair in res.split("\n") {
|
||||
for pair in res.split('\n') {
|
||||
if pair.starts_with(&name) {
|
||||
let res = &pair[name.len()..];
|
||||
return f64::from_str(&res).ok();
|
||||
return f64::from_str(res).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,18 +81,13 @@ impl XConnection {
|
||||
// XRROutputInfo contains an array of mode ids that correspond to
|
||||
// modes in the array in XRRScreenResources
|
||||
.filter(|x| output_modes.iter().any(|id| x.id == *id))
|
||||
.map(|x| {
|
||||
let refresh_rate = if x.dotClock > 0 && x.hTotal > 0 && x.vTotal > 0 {
|
||||
x.dotClock as u64 * 1000 / (x.hTotal as u64 * x.vTotal as u64)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
.map(|mode| {
|
||||
VideoMode {
|
||||
size: (x.width, x.height),
|
||||
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
|
||||
size: (mode.width, mode.height),
|
||||
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode)
|
||||
.unwrap_or(0),
|
||||
bit_depth: bit_depth as u16,
|
||||
native_mode: x.id,
|
||||
native_mode: mode.id,
|
||||
// This is populated in `MonitorHandle::video_modes` as the
|
||||
// video mode is returned to the user
|
||||
monitor: None,
|
||||
@@ -164,7 +160,9 @@ impl XConnection {
|
||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
||||
Some((name, scale_factor, modes))
|
||||
}
|
||||
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
|
||||
|
||||
#[must_use]
|
||||
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> {
|
||||
unsafe {
|
||||
let mut major = 0;
|
||||
let mut minor = 0;
|
||||
@@ -195,12 +193,13 @@ impl XConnection {
|
||||
(self.xrandr.XRRFreeScreenResources)(resources);
|
||||
|
||||
if status == Success as i32 {
|
||||
Ok(())
|
||||
Some(())
|
||||
} else {
|
||||
Err(())
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
|
||||
unsafe {
|
||||
let mut major = 0;
|
||||
|
||||
@@ -58,7 +58,7 @@ impl XConnection {
|
||||
property,
|
||||
// This offset is in terms of 32-bit chunks.
|
||||
offset,
|
||||
// This is the quanity of 32-bit chunks to receive at once.
|
||||
// This is the quantity of 32-bit chunks to receive at once.
|
||||
PROPERTY_BUFFER_SIZE,
|
||||
ffi::False,
|
||||
property_type,
|
||||
@@ -97,7 +97,7 @@ impl XConnection {
|
||||
quantity_returned,
|
||||
new_data,
|
||||
);*/
|
||||
data.extend_from_slice(&new_data);
|
||||
data.extend_from_slice(new_data);
|
||||
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
|
||||
(self.xlib.XFree)(buf as _); // Don't try to access new_data after this.
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::*;
|
||||
|
||||
// This info is global to the window manager.
|
||||
lazy_static! {
|
||||
static ref SUPPORTED_HINTS: Mutex<Vec<ffi::Atom>> = Mutex::new(Vec::with_capacity(0));
|
||||
static ref WM_NAME: Mutex<Option<String>> = Mutex::new(None);
|
||||
}
|
||||
static SUPPORTED_HINTS: Lazy<Mutex<Vec<ffi::Atom>>> =
|
||||
Lazy::new(|| Mutex::new(Vec::with_capacity(0)));
|
||||
static WM_NAME: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
pub fn hint_is_supported(hint: ffi::Atom) -> bool {
|
||||
(*SUPPORTED_HINTS.lock()).contains(&hint)
|
||||
@@ -60,13 +60,9 @@ impl XConnection {
|
||||
let root_window_wm_check = {
|
||||
let result = self.get_property(root, check_atom, ffi::XA_WINDOW);
|
||||
|
||||
let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned());
|
||||
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
|
||||
|
||||
if let Some(wm_check) = wm_check {
|
||||
wm_check
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
wm_check?
|
||||
};
|
||||
|
||||
// Querying the same property on the child window we were given, we should get this child
|
||||
@@ -74,13 +70,9 @@ impl XConnection {
|
||||
let child_window_wm_check = {
|
||||
let result = self.get_property(root_window_wm_check, check_atom, ffi::XA_WINDOW);
|
||||
|
||||
let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned());
|
||||
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
|
||||
|
||||
if let Some(wm_check) = wm_check {
|
||||
wm_check
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
wm_check?
|
||||
};
|
||||
|
||||
// These values should be the same.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use raw_window_handle::XlibHandle;
|
||||
use std::{
|
||||
cmp, env,
|
||||
ffi::CString,
|
||||
@@ -8,10 +7,11 @@ use std::{
|
||||
ptr, slice,
|
||||
sync::Arc,
|
||||
};
|
||||
use x11_dl::xlib::TrueColor;
|
||||
|
||||
use libc;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle};
|
||||
use x11_dl::xlib::TrueColor;
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
@@ -22,11 +22,12 @@ use crate::{
|
||||
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
|
||||
VideoMode as PlatformVideoMode,
|
||||
},
|
||||
window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
|
||||
};
|
||||
|
||||
use super::{
|
||||
ffi, util, EventLoopWindowTarget, ImeSender, WakeSender, WindowId, XConnection, XError,
|
||||
ffi, util, EventLoopWindowTarget, ImeRequest, ImeSender, WakeSender, WindowId, XConnection,
|
||||
XError,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -36,6 +37,8 @@ pub struct SharedState {
|
||||
pub position: Option<(i32, i32)>,
|
||||
pub inner_position: Option<(i32, i32)>,
|
||||
pub inner_position_rel_parent: Option<(i32, i32)>,
|
||||
pub is_resizable: bool,
|
||||
pub is_decorated: bool,
|
||||
pub last_monitor: X11MonitorHandle,
|
||||
pub dpi_adjusted: Option<(u32, u32)>,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
@@ -62,8 +65,8 @@ pub enum Visibility {
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
fn new(last_monitor: X11MonitorHandle, is_visible: bool) -> Mutex<Self> {
|
||||
let visibility = if is_visible {
|
||||
fn new(last_monitor: X11MonitorHandle, window_attributes: &WindowAttributes) -> Mutex<Self> {
|
||||
let visibility = if window_attributes.visible {
|
||||
Visibility::YesWait
|
||||
} else {
|
||||
Visibility::No
|
||||
@@ -73,6 +76,8 @@ impl SharedState {
|
||||
last_monitor,
|
||||
visibility,
|
||||
|
||||
is_resizable: window_attributes.resizable,
|
||||
is_decorated: window_attributes.decorations,
|
||||
cursor_pos: None,
|
||||
size: None,
|
||||
position: None,
|
||||
@@ -101,7 +106,7 @@ pub struct UnownedWindow {
|
||||
root: ffi::Window, // never changes
|
||||
screen_id: i32, // never changes
|
||||
cursor: Mutex<CursorIcon>,
|
||||
cursor_grabbed: Mutex<bool>,
|
||||
cursor_grabbed_mode: Mutex<CursorGrabMode>,
|
||||
cursor_visible: Mutex<bool>,
|
||||
ime_sender: Mutex<ImeSender>,
|
||||
pub shared_state: Mutex<SharedState>,
|
||||
@@ -109,7 +114,7 @@ pub struct UnownedWindow {
|
||||
}
|
||||
|
||||
impl UnownedWindow {
|
||||
pub fn new<T>(
|
||||
pub(crate) fn new<T>(
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
window_attrs: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
@@ -150,7 +155,7 @@ impl UnownedWindow {
|
||||
|
||||
let position = window_attrs
|
||||
.position
|
||||
.map(|position| position.to_physical::<i32>(scale_factor).into());
|
||||
.map(|position| position.to_physical::<i32>(scale_factor));
|
||||
|
||||
let dimensions = {
|
||||
// x11 only applies constraints when the window is actively resized
|
||||
@@ -184,7 +189,7 @@ impl UnownedWindow {
|
||||
// creating
|
||||
let (visual, depth, require_colormap) = match pl_attribs.visual_infos {
|
||||
Some(vi) => (vi.visual, vi.depth, false),
|
||||
None if window_attrs.transparent == true => {
|
||||
None if window_attrs.transparent => {
|
||||
// Find a suitable visual
|
||||
let mut vinfo = MaybeUninit::uninit();
|
||||
let vinfo_initialized = unsafe {
|
||||
@@ -271,10 +276,10 @@ impl UnownedWindow {
|
||||
root,
|
||||
screen_id,
|
||||
cursor: Default::default(),
|
||||
cursor_grabbed: Mutex::new(false),
|
||||
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
|
||||
cursor_visible: Mutex::new(true),
|
||||
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
|
||||
shared_state: SharedState::new(guessed_monitor, window_attrs.visible),
|
||||
shared_state: SharedState::new(guessed_monitor, &window_attrs),
|
||||
redraw_sender: WakeSender {
|
||||
waker: event_loop.redraw_sender.waker.clone(),
|
||||
sender: event_loop.redraw_sender.sender.clone(),
|
||||
@@ -306,11 +311,11 @@ impl UnownedWindow {
|
||||
|
||||
// WM_CLASS must be set *before* mapping the window, as per ICCCM!
|
||||
{
|
||||
let (class, instance) = if let Some((instance, class)) = pl_attribs.class {
|
||||
let instance = CString::new(instance.as_str())
|
||||
let (class, instance) = if let Some(name) = pl_attribs.name {
|
||||
let instance = CString::new(name.instance.as_str())
|
||||
.expect("`WM_CLASS` instance contained null byte");
|
||||
let class =
|
||||
CString::new(class.as_str()).expect("`WM_CLASS` class contained null byte");
|
||||
let class = CString::new(name.general.as_str())
|
||||
.expect("`WM_CLASS` class contained null byte");
|
||||
(instance, class)
|
||||
} else {
|
||||
let class = env::args()
|
||||
@@ -341,7 +346,9 @@ impl UnownedWindow {
|
||||
} //.queue();
|
||||
}
|
||||
|
||||
window.set_pid().map(|flusher| flusher.queue());
|
||||
if let Some(flusher) = window.set_pid() {
|
||||
flusher.queue()
|
||||
}
|
||||
|
||||
window.set_window_types(pl_attribs.x11_window_types).queue();
|
||||
|
||||
@@ -430,8 +437,7 @@ impl UnownedWindow {
|
||||
}
|
||||
|
||||
// Select XInput2 events
|
||||
let mask = {
|
||||
let mask = ffi::XI_MotionMask
|
||||
let mask = ffi::XI_MotionMask
|
||||
| ffi::XI_ButtonPressMask
|
||||
| ffi::XI_ButtonReleaseMask
|
||||
//| ffi::XI_KeyPressMask
|
||||
@@ -443,14 +449,15 @@ impl UnownedWindow {
|
||||
| ffi::XI_TouchBeginMask
|
||||
| ffi::XI_TouchUpdateMask
|
||||
| ffi::XI_TouchEndMask;
|
||||
mask
|
||||
};
|
||||
xconn
|
||||
.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask)
|
||||
.queue();
|
||||
|
||||
{
|
||||
let result = event_loop.ime.borrow_mut().create_context(window.xwindow);
|
||||
let result = event_loop
|
||||
.ime
|
||||
.borrow_mut()
|
||||
.create_context(window.xwindow, false);
|
||||
if let Err(err) = result {
|
||||
let e = match err {
|
||||
ImeContextCreationError::XError(err) => OsError::XError(err),
|
||||
@@ -467,9 +474,10 @@ impl UnownedWindow {
|
||||
window.set_maximized_inner(window_attrs.maximized).queue();
|
||||
}
|
||||
if window_attrs.fullscreen.is_some() {
|
||||
window
|
||||
.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
||||
.map(|flusher| flusher.queue());
|
||||
if let Some(flusher) = window.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
||||
{
|
||||
flusher.queue()
|
||||
}
|
||||
|
||||
if let Some(PhysicalPosition { x, y }) = position {
|
||||
let shared_state = window.shared_state.get_mut();
|
||||
@@ -880,6 +888,7 @@ impl UnownedWindow {
|
||||
}
|
||||
|
||||
fn set_decorations_inner(&self, decorations: bool) -> util::Flusher<'_> {
|
||||
self.shared_state.lock().is_decorated = decorations;
|
||||
let mut hints = self.xconn.get_motif_hints(self.xwindow);
|
||||
|
||||
hints.set_decorations(decorations);
|
||||
@@ -895,6 +904,11 @@ impl UnownedWindow {
|
||||
self.invalidate_cached_frame_extents();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
self.shared_state.lock().is_decorated
|
||||
}
|
||||
|
||||
fn set_maximizable_inner(&self, maximizable: bool) -> util::Flusher<'_> {
|
||||
let mut hints = self.xconn.get_motif_hints(self.xwindow);
|
||||
|
||||
@@ -979,6 +993,11 @@ impl UnownedWindow {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
Some(self.shared_state.lock().visibility == Visibility::Yes)
|
||||
}
|
||||
|
||||
fn update_cached_frame_extents(&self) {
|
||||
let extents = self
|
||||
.xconn
|
||||
@@ -1100,8 +1119,15 @@ impl UnownedWindow {
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let (width, height) = size.to_physical::<u32>(scale_factor).into();
|
||||
self.set_inner_size_physical(width, height);
|
||||
let size = size.to_physical::<u32>(scale_factor).into();
|
||||
if !self.shared_state.lock().is_resizable {
|
||||
self.update_normal_hints(|normal_hints| {
|
||||
normal_hints.set_min_size(Some(size));
|
||||
normal_hints.set_max_size(Some(size));
|
||||
})
|
||||
.expect("Failed to call `XSetWMNormalHints`");
|
||||
}
|
||||
self.set_inner_size_physical(size.0, size.1);
|
||||
}
|
||||
|
||||
fn update_normal_hints<F>(&self, callback: F) -> Result<(), XError>
|
||||
@@ -1189,6 +1215,7 @@ impl UnownedWindow {
|
||||
let window_size = Some(Size::from(self.inner_size()));
|
||||
(window_size, window_size)
|
||||
};
|
||||
self.shared_state.lock().is_resizable = resizable;
|
||||
|
||||
self.set_maximizable_inner(resizable).queue();
|
||||
|
||||
@@ -1206,6 +1233,11 @@ impl UnownedWindow {
|
||||
.expect("Failed to call `XSetWMNormalHints`");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
self.shared_state.lock().is_resizable
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn xlib_display(&self) -> *mut c_void {
|
||||
self.xconn.display as _
|
||||
@@ -1240,64 +1272,75 @@ impl UnownedWindow {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
let mut grabbed_lock = self.cursor_grabbed.lock();
|
||||
if grab == *grabbed_lock {
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
let mut grabbed_lock = self.cursor_grabbed_mode.lock();
|
||||
if mode == *grabbed_lock {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`.
|
||||
// Therefore, this is common to both codepaths.
|
||||
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
|
||||
}
|
||||
let result = if grab {
|
||||
let result = unsafe {
|
||||
(self.xconn.xlib.XGrabPointer)(
|
||||
self.xconn.display,
|
||||
self.xwindow,
|
||||
ffi::True,
|
||||
(ffi::ButtonPressMask
|
||||
| ffi::ButtonReleaseMask
|
||||
| ffi::EnterWindowMask
|
||||
| ffi::LeaveWindowMask
|
||||
| ffi::PointerMotionMask
|
||||
| ffi::PointerMotionHintMask
|
||||
| ffi::Button1MotionMask
|
||||
| ffi::Button2MotionMask
|
||||
| ffi::Button3MotionMask
|
||||
| ffi::Button4MotionMask
|
||||
| ffi::Button5MotionMask
|
||||
| ffi::ButtonMotionMask
|
||||
| ffi::KeymapStateMask) as c_uint,
|
||||
ffi::GrabModeAsync,
|
||||
ffi::GrabModeAsync,
|
||||
self.xwindow,
|
||||
0,
|
||||
ffi::CurrentTime,
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
ffi::GrabSuccess => Ok(()),
|
||||
ffi::AlreadyGrabbed => {
|
||||
Err("Cursor could not be grabbed: already grabbed by another client")
|
||||
}
|
||||
ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"),
|
||||
ffi::GrabNotViewable => {
|
||||
Err("Cursor could not be grabbed: grab location not viewable")
|
||||
}
|
||||
ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err))))
|
||||
} else {
|
||||
self.xconn
|
||||
let result = match mode {
|
||||
CursorGrabMode::None => self
|
||||
.xconn
|
||||
.flush_requests()
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))),
|
||||
CursorGrabMode::Confined => {
|
||||
let result = unsafe {
|
||||
(self.xconn.xlib.XGrabPointer)(
|
||||
self.xconn.display,
|
||||
self.xwindow,
|
||||
ffi::True,
|
||||
(ffi::ButtonPressMask
|
||||
| ffi::ButtonReleaseMask
|
||||
| ffi::EnterWindowMask
|
||||
| ffi::LeaveWindowMask
|
||||
| ffi::PointerMotionMask
|
||||
| ffi::PointerMotionHintMask
|
||||
| ffi::Button1MotionMask
|
||||
| ffi::Button2MotionMask
|
||||
| ffi::Button3MotionMask
|
||||
| ffi::Button4MotionMask
|
||||
| ffi::Button5MotionMask
|
||||
| ffi::ButtonMotionMask
|
||||
| ffi::KeymapStateMask) as c_uint,
|
||||
ffi::GrabModeAsync,
|
||||
ffi::GrabModeAsync,
|
||||
self.xwindow,
|
||||
0,
|
||||
ffi::CurrentTime,
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
ffi::GrabSuccess => Ok(()),
|
||||
ffi::AlreadyGrabbed => {
|
||||
Err("Cursor could not be confined: already confined by another client")
|
||||
}
|
||||
ffi::GrabInvalidTime => Err("Cursor could not be confined: invalid time"),
|
||||
ffi::GrabNotViewable => {
|
||||
Err("Cursor could not be confined: confine location not viewable")
|
||||
}
|
||||
ffi::GrabFrozen => {
|
||||
Err("Cursor could not be confined: frozen by another client")
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err))))
|
||||
}
|
||||
CursorGrabMode::Locked => {
|
||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
||||
}
|
||||
};
|
||||
|
||||
if result.is_ok() {
|
||||
*grabbed_lock = grab;
|
||||
*grabbed_lock = mode;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@@ -1337,6 +1380,11 @@ impl UnownedWindow {
|
||||
self.set_cursor_position_physical(x, y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
let pointer = self
|
||||
.xconn
|
||||
@@ -1349,14 +1397,14 @@ impl UnownedWindow {
|
||||
|
||||
// we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
|
||||
// if the cursor isn't currently grabbed
|
||||
let mut grabbed_lock = self.cursor_grabbed.lock();
|
||||
let mut grabbed_lock = self.cursor_grabbed_mode.lock();
|
||||
unsafe {
|
||||
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
|
||||
}
|
||||
self.xconn
|
||||
.flush_requests()
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
|
||||
*grabbed_lock = false;
|
||||
*grabbed_lock = CursorGrabMode::None;
|
||||
|
||||
// we keep the lock until we are done
|
||||
self.xconn
|
||||
@@ -1377,17 +1425,21 @@ impl UnownedWindow {
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
|
||||
}
|
||||
|
||||
pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
|
||||
let _ = self
|
||||
.ime_sender
|
||||
.lock()
|
||||
.send((self.xwindow, x as i16, y as i16));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, spot: Position) {
|
||||
let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
|
||||
self.set_ime_position_physical(x, y);
|
||||
let _ = self
|
||||
.ime_sender
|
||||
.lock()
|
||||
.send(ImeRequest::Position(self.xwindow, x, y));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
let _ = self
|
||||
.ime_sender
|
||||
.lock()
|
||||
.send(ImeRequest::Allow(self.xwindow, allowed));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1444,23 +1496,30 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId(self.xwindow)
|
||||
WindowId(self.xwindow as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
self.redraw_sender
|
||||
.sender
|
||||
.send(WindowId(self.xwindow))
|
||||
.send(WindowId(self.xwindow as u64))
|
||||
.unwrap();
|
||||
self.redraw_sender.waker.wake().unwrap();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> XlibHandle {
|
||||
let mut handle = XlibHandle::empty();
|
||||
handle.window = self.xlib_window();
|
||||
handle.display = self.xlib_display();
|
||||
handle
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut window_handle = XlibWindowHandle::empty();
|
||||
window_handle.window = self.xlib_window();
|
||||
RawWindowHandle::Xlib(window_handle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = XlibDisplayHandle::empty();
|
||||
display_handle.display = self.xlib_display();
|
||||
display_handle.screen = self.screen_id;
|
||||
RawDisplayHandle::Xlib(display_handle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID};
|
||||
use crate::event::{DeviceEvent, ElementState, Event};
|
||||
@@ -16,19 +17,17 @@ pub struct AppClass(pub *const Class);
|
||||
unsafe impl Send for AppClass {}
|
||||
unsafe impl Sync for AppClass {}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref APP_CLASS: AppClass = unsafe {
|
||||
let superclass = class!(NSApplication);
|
||||
let mut decl = ClassDecl::new("WinitApp", superclass).unwrap();
|
||||
pub static APP_CLASS: Lazy<AppClass> = Lazy::new(|| unsafe {
|
||||
let superclass = class!(NSApplication);
|
||||
let mut decl = ClassDecl::new("WinitApp", superclass).unwrap();
|
||||
|
||||
decl.add_method(
|
||||
sel!(sendEvent:),
|
||||
send_event as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(sendEvent:),
|
||||
send_event as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
AppClass(decl.register())
|
||||
};
|
||||
}
|
||||
AppClass(decl.register())
|
||||
});
|
||||
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
os::raw::c_void,
|
||||
};
|
||||
|
||||
use cocoa::base::id;
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
os::raw::c_void,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
|
||||
|
||||
static AUX_DELEGATE_STATE_NAME: &str = "auxState";
|
||||
|
||||
pub struct AuxDelegateState {
|
||||
/// We store this value in order to be able to defer setting the activation policy until
|
||||
/// after the app has finished launching. If the activation policy is set earlier, the
|
||||
/// menubar is initially unresponsive on macOS 10.15 for example.
|
||||
pub activation_policy: ActivationPolicy,
|
||||
|
||||
pub create_default_menu: bool,
|
||||
pub default_menu: bool,
|
||||
}
|
||||
|
||||
pub struct AppDelegateClass(pub *const Class);
|
||||
unsafe impl Send for AppDelegateClass {}
|
||||
unsafe impl Sync for AppDelegateClass {}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
|
||||
let superclass = class!(NSResponder);
|
||||
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
|
||||
pub static APP_DELEGATE_CLASS: Lazy<AppDelegateClass> = Lazy::new(|| unsafe {
|
||||
let superclass = class!(NSResponder);
|
||||
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
|
||||
|
||||
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
|
||||
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
|
||||
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
|
||||
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
|
||||
|
||||
decl.add_method(
|
||||
sel!(applicationDidFinishLaunching:),
|
||||
did_finish_launching as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
|
||||
decl.add_method(
|
||||
sel!(applicationDidFinishLaunching:),
|
||||
did_finish_launching as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillTerminate:),
|
||||
will_terminate as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
};
|
||||
}
|
||||
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
});
|
||||
|
||||
/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS
|
||||
pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> {
|
||||
@@ -54,11 +55,12 @@ extern "C" fn new(class: &Class, _: Sel) -> id {
|
||||
unsafe {
|
||||
let this: id = msg_send![class, alloc];
|
||||
let this: id = msg_send![this, init];
|
||||
// TODO: Remove the need for this initialization here
|
||||
(*this).set_ivar(
|
||||
AUX_DELEGATE_STATE_NAME,
|
||||
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
|
||||
activation_policy: ActivationPolicy::Regular,
|
||||
create_default_menu: true,
|
||||
default_menu: true,
|
||||
}))) as *mut c_void,
|
||||
);
|
||||
this
|
||||
@@ -70,12 +72,18 @@ extern "C" fn dealloc(this: &Object, _: Sel) {
|
||||
let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME));
|
||||
// As soon as the box is constructed it is immediately dropped, releasing the underlying
|
||||
// memory
|
||||
Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>);
|
||||
drop(Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidFinishLaunching`");
|
||||
trace_scope!("applicationDidFinishLaunching:");
|
||||
AppState::launched(this);
|
||||
trace!("Completed `applicationDidFinishLaunching`");
|
||||
}
|
||||
|
||||
extern "C" fn will_terminate(_this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationWillTerminate`");
|
||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
||||
AppState::exit();
|
||||
trace!("Completed `applicationWillTerminate`");
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ use cocoa::{
|
||||
};
|
||||
use objc::{
|
||||
rc::autoreleasepool,
|
||||
runtime::{Object, YES},
|
||||
runtime::{Object, BOOL, NO, YES},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
@@ -41,9 +42,7 @@ use crate::{
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref HANDLER: Handler = Default::default();
|
||||
}
|
||||
static HANDLER: Lazy<Handler> = Lazy::new(Default::default);
|
||||
|
||||
impl<'a, Never> Event<'a, Never> {
|
||||
fn userify<T: 'static>(self) -> Event<'a, T> {
|
||||
@@ -60,9 +59,11 @@ pub trait EventHandler: Debug {
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
||||
}
|
||||
|
||||
pub(crate) type Callback<T> =
|
||||
RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>;
|
||||
|
||||
struct EventLoopHandler<T: 'static> {
|
||||
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
||||
will_exit: bool,
|
||||
callback: Weak<Callback<T>>,
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
}
|
||||
|
||||
@@ -98,25 +99,25 @@ impl<T> Debug for EventLoopHandler<T> {
|
||||
impl<T> EventHandler for EventLoopHandler<T> {
|
||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
|
||||
self.with_callback(|this, mut callback| {
|
||||
(callback)(event.userify(), &this.window_target, control_flow);
|
||||
this.will_exit |= *control_flow == ControlFlow::Exit;
|
||||
if this.will_exit {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
||||
let dummy = &mut ControlFlow::ExitWithCode(code);
|
||||
(callback)(event.userify(), &this.window_target, dummy);
|
||||
} else {
|
||||
(callback)(event.userify(), &this.window_target, control_flow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
|
||||
self.with_callback(|this, mut callback| {
|
||||
let mut will_exit = this.will_exit;
|
||||
for event in this.window_target.p.receiver.try_iter() {
|
||||
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
|
||||
will_exit |= *control_flow == ControlFlow::Exit;
|
||||
if will_exit {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
||||
let dummy = &mut ControlFlow::ExitWithCode(code);
|
||||
(callback)(Event::UserEvent(event), &this.window_target, dummy);
|
||||
} else {
|
||||
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
|
||||
}
|
||||
}
|
||||
this.will_exit = will_exit;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -125,7 +126,6 @@ impl<T> EventHandler for EventLoopHandler<T> {
|
||||
struct Handler {
|
||||
ready: AtomicBool,
|
||||
in_callback: AtomicBool,
|
||||
dialog_is_closing: AtomicBool,
|
||||
control_flow: Mutex<ControlFlow>,
|
||||
control_flow_prev: Mutex<ControlFlow>,
|
||||
start_time: Mutex<Option<Instant>>,
|
||||
@@ -143,7 +143,7 @@ impl Handler {
|
||||
self.pending_events.lock().unwrap()
|
||||
}
|
||||
|
||||
fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
|
||||
fn redraw(&self) -> MutexGuard<'_, Vec<WindowId>> {
|
||||
self.pending_redraw.lock().unwrap()
|
||||
}
|
||||
|
||||
@@ -160,7 +160,10 @@ impl Handler {
|
||||
}
|
||||
|
||||
fn should_exit(&self) -> bool {
|
||||
*self.control_flow.lock().unwrap() == ControlFlow::Exit
|
||||
matches!(
|
||||
*self.control_flow.lock().unwrap(),
|
||||
ControlFlow::ExitWithCode(_)
|
||||
)
|
||||
}
|
||||
|
||||
fn get_control_flow_and_update_prev(&self) -> ControlFlow {
|
||||
@@ -184,11 +187,11 @@ impl Handler {
|
||||
}
|
||||
|
||||
fn take_events(&self) -> VecDeque<EventWrapper> {
|
||||
mem::replace(&mut *self.events(), Default::default())
|
||||
mem::take(&mut *self.events())
|
||||
}
|
||||
|
||||
fn should_redraw(&self) -> Vec<WindowId> {
|
||||
mem::replace(&mut *self.redraw(), Default::default())
|
||||
mem::take(&mut *self.redraw())
|
||||
}
|
||||
|
||||
fn get_in_callback(&self) -> bool {
|
||||
@@ -257,27 +260,26 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
pub static INTERRUPT_EVENT_LOOP_EXIT: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub enum AppState {}
|
||||
|
||||
impl AppState {
|
||||
pub fn set_callback<T>(
|
||||
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
) {
|
||||
pub fn set_callback<T>(callback: Weak<Callback<T>>, window_target: Rc<RootWindowTarget<T>>) {
|
||||
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
||||
callback,
|
||||
will_exit: false,
|
||||
window_target,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn exit() {
|
||||
pub fn exit() -> i32 {
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed));
|
||||
HANDLER.set_in_callback(false);
|
||||
HANDLER.callback.lock().unwrap().take();
|
||||
if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 {
|
||||
code
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launched(app_delegate: &Object) {
|
||||
@@ -290,7 +292,7 @@ impl AppState {
|
||||
};
|
||||
HANDLER.set_ready();
|
||||
HANDLER.waker().start();
|
||||
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).create_default_menu };
|
||||
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).default_menu };
|
||||
if create_default_menu {
|
||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||
// overriding of the default menu even if it's created
|
||||
@@ -300,6 +302,9 @@ impl AppState {
|
||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
|
||||
StartCause::Init,
|
||||
)));
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed));
|
||||
HANDLER.set_in_callback(false);
|
||||
}
|
||||
|
||||
@@ -332,7 +337,7 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlow::Exit => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
|
||||
ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
|
||||
};
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause)));
|
||||
@@ -356,14 +361,16 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub fn queue_event(wrapper: EventWrapper) {
|
||||
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
|
||||
let is_main_thread: BOOL = unsafe { msg_send!(class!(NSThread), isMainThread) };
|
||||
if is_main_thread == NO {
|
||||
panic!("Event queued from different thread: {:#?}", wrapper);
|
||||
}
|
||||
HANDLER.events().push_back(wrapper);
|
||||
}
|
||||
|
||||
pub fn queue_events(mut wrappers: VecDeque<EventWrapper>) {
|
||||
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
|
||||
let is_main_thread: BOOL = unsafe { msg_send!(class!(NSThread), isMainThread) };
|
||||
if is_main_thread == NO {
|
||||
panic!("Events queued from different thread: {:#?}", wrappers);
|
||||
}
|
||||
HANDLER.events().append(&mut wrappers);
|
||||
@@ -395,44 +402,17 @@ impl AppState {
|
||||
if HANDLER.should_exit() {
|
||||
unsafe {
|
||||
let app: id = NSApp();
|
||||
let windows: id = msg_send![app, windows];
|
||||
let window_count: usize = msg_send![windows, count];
|
||||
|
||||
let dialog_open = if window_count > 1 {
|
||||
let dialog: id = msg_send![windows, lastObject];
|
||||
let is_main_window: bool = msg_send![dialog, isMainWindow];
|
||||
msg_send![dialog, isVisible] && !is_main_window
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let dialog_is_closing = HANDLER.dialog_is_closing.load(Ordering::SeqCst);
|
||||
autoreleasepool(|| {
|
||||
if !INTERRUPT_EVENT_LOOP_EXIT.load(Ordering::SeqCst)
|
||||
&& !dialog_open
|
||||
&& !dialog_is_closing
|
||||
{
|
||||
let () = msg_send![app, stop: nil];
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
post_dummy_event(app);
|
||||
}
|
||||
let _: () = msg_send![app, stop: nil];
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
post_dummy_event(app);
|
||||
});
|
||||
|
||||
if window_count > 0 {
|
||||
let window: id = msg_send![windows, objectAtIndex:0];
|
||||
let window_has_focus = msg_send![window, isKeyWindow];
|
||||
if !dialog_open && window_has_focus && dialog_is_closing {
|
||||
HANDLER.dialog_is_closing.store(false, Ordering::SeqCst);
|
||||
}
|
||||
if dialog_open {
|
||||
HANDLER.dialog_is_closing.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
HANDLER.update_start_time();
|
||||
match HANDLER.get_old_and_new_control_flow() {
|
||||
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (),
|
||||
(ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (),
|
||||
(old, new) if old == new => (),
|
||||
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
|
||||
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),
|
||||
@@ -477,7 +457,7 @@ fn apply_activation_policy(app_delegate: &Object) {
|
||||
let ns_app = NSApp();
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar won't be interactable.
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
let act_pol = get_aux_state_mut(app_delegate).activation_policy;
|
||||
ns_app.setActivationPolicy_(match act_pol {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
||||
|
||||
@@ -12,23 +12,28 @@ use std::{
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSEventType::NSApplicationDefined},
|
||||
base::{id, nil, YES},
|
||||
foundation::NSPoint,
|
||||
appkit::{NSApp, NSEventModifierFlags, NSEventSubtype, NSEventType::NSApplicationDefined},
|
||||
base::{id, nil, BOOL, NO, YES},
|
||||
foundation::{NSInteger, NSPoint, NSTimeInterval},
|
||||
};
|
||||
use objc::rc::autoreleasepool;
|
||||
use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform_impl::platform::{
|
||||
app::APP_CLASS,
|
||||
app_delegate::APP_DELEGATE_CLASS,
|
||||
app_state::AppState,
|
||||
monitor::{self, MonitorHandle},
|
||||
observer::*,
|
||||
util::IdRef,
|
||||
platform::macos::ActivationPolicy,
|
||||
platform_impl::{
|
||||
get_aux_state_mut,
|
||||
platform::{
|
||||
app::APP_CLASS,
|
||||
app_delegate::APP_DELEGATE_CLASS,
|
||||
app_state::{AppState, Callback},
|
||||
monitor::{self, MonitorHandle},
|
||||
observer::*,
|
||||
util::IdRef,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -83,6 +88,11 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
let monitor = monitor::primary_monitor();
|
||||
Some(RootMonitorHandle { inner: monitor })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
@@ -100,7 +110,9 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
pub(crate) delegate: IdRef,
|
||||
/// The delegate is only weakly referenced by NSApplication, so we keep
|
||||
/// it around here as well.
|
||||
_delegate: IdRef,
|
||||
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
panic_info: Rc<PanicInfo>,
|
||||
@@ -111,13 +123,29 @@ pub struct EventLoop<T: 'static> {
|
||||
/// Every other reference should be a Weak reference which is only upgraded
|
||||
/// into a strong reference in order to call the callback but then the
|
||||
/// strong reference should be dropped as soon as possible.
|
||||
_callback: Option<Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>>,
|
||||
_callback: Option<Rc<Callback<T>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
||||
pub(crate) activation_policy: ActivationPolicy,
|
||||
pub(crate) default_menu: bool,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificEventLoopAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
activation_policy: Default::default(), // Regular
|
||||
default_menu: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
|
||||
let delegate = unsafe {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
|
||||
if is_main_thread == NO {
|
||||
panic!("On macOS, `EventLoop` must be created on the main thread!");
|
||||
}
|
||||
|
||||
@@ -128,15 +156,21 @@ impl<T> EventLoop<T> {
|
||||
let app: id = msg_send![APP_CLASS.0, sharedApplication];
|
||||
|
||||
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]);
|
||||
|
||||
let mut aux_state = get_aux_state_mut(&**delegate);
|
||||
aux_state.activation_policy = attributes.activation_policy;
|
||||
aux_state.default_menu = attributes.default_menu;
|
||||
|
||||
autoreleasepool(|| {
|
||||
let _: () = msg_send![app, setDelegate:*delegate];
|
||||
});
|
||||
|
||||
delegate
|
||||
};
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
setup_control_flow_observers(Rc::downgrade(&panic_info));
|
||||
EventLoop {
|
||||
delegate,
|
||||
_delegate: delegate,
|
||||
window_target: Rc::new(RootWindowTarget {
|
||||
p: Default::default(),
|
||||
_marker: PhantomData,
|
||||
@@ -154,11 +188,11 @@ impl<T> EventLoop<T> {
|
||||
where
|
||||
F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
self.run_return(callback);
|
||||
process::exit(0);
|
||||
let exit_code = self.run_return(callback);
|
||||
process::exit(exit_code);
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, callback: F)
|
||||
pub fn run_return<F>(&mut self, callback: F) -> i32
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
@@ -175,29 +209,31 @@ impl<T> EventLoop<T> {
|
||||
|
||||
self._callback = Some(Rc::clone(&callback));
|
||||
|
||||
autoreleasepool(|| unsafe {
|
||||
let exit_code = autoreleasepool(|| unsafe {
|
||||
let app = NSApp();
|
||||
assert_ne!(app, nil);
|
||||
|
||||
// A bit of juggling with the callback references to make sure
|
||||
// that `self.callback` is the only owner of the callback.
|
||||
let weak_cb: Weak<_> = Rc::downgrade(&callback);
|
||||
mem::drop(callback);
|
||||
drop(callback);
|
||||
|
||||
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
|
||||
let () = msg_send![app, run];
|
||||
let _: () = msg_send![app, run];
|
||||
|
||||
if let Some(panic) = self.panic_info.take() {
|
||||
drop(self._callback.take());
|
||||
resume_unwind(panic);
|
||||
}
|
||||
AppState::exit();
|
||||
AppState::exit()
|
||||
});
|
||||
drop(self._callback.take());
|
||||
|
||||
exit_code
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> Proxy<T> {
|
||||
Proxy::new(self.window_target.p.sender.clone())
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy::new(self.window_target.p.sender.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,15 +244,15 @@ pub unsafe fn post_dummy_event(target: id) {
|
||||
event_class,
|
||||
otherEventWithType: NSApplicationDefined
|
||||
location: NSPoint::new(0.0, 0.0)
|
||||
modifierFlags: 0
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
modifierFlags: NSEventModifierFlags::empty()
|
||||
timestamp: 0 as NSTimeInterval
|
||||
windowNumber: 0 as NSInteger
|
||||
context: nil
|
||||
subtype: 0
|
||||
data1: 0
|
||||
data2: 0
|
||||
subtype: NSEventSubtype::NSWindowExposedEventType
|
||||
data1: 0 as NSInteger
|
||||
data2: 0 as NSInteger
|
||||
];
|
||||
let () = msg_send![target, postEvent: dummy_event atStart: YES];
|
||||
let _: () = msg_send![target, postEvent: dummy_event atStart: YES];
|
||||
}
|
||||
|
||||
/// Catches panics that happen inside `f` and when a panic
|
||||
@@ -240,7 +276,7 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
||||
unsafe {
|
||||
let app_class = class!(NSApplication);
|
||||
let app: id = msg_send![app_class, sharedApplication];
|
||||
let () = msg_send![app, stop: nil];
|
||||
let _: () = msg_send![app, stop: nil];
|
||||
|
||||
// Posting a dummy event to get `stop` to take effect immediately.
|
||||
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
||||
@@ -251,14 +287,14 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Proxy<T> {
|
||||
pub struct EventLoopProxy<T> {
|
||||
sender: mpsc::Sender<T>,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for Proxy<T> {}
|
||||
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
||||
|
||||
impl<T> Drop for Proxy<T> {
|
||||
impl<T> Drop for EventLoopProxy<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRelease(self.source as _);
|
||||
@@ -266,13 +302,13 @@ impl<T> Drop for Proxy<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Proxy<T> {
|
||||
impl<T> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Proxy::new(self.sender.clone())
|
||||
EventLoopProxy::new(self.sender.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Proxy<T> {
|
||||
impl<T> EventLoopProxy<T> {
|
||||
fn new(sender: mpsc::Sender<T>) -> Self {
|
||||
unsafe {
|
||||
// just wake up the eventloop
|
||||
@@ -288,7 +324,7 @@ impl<T> Proxy<T> {
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
Proxy { sender, source }
|
||||
EventLoopProxy { sender, source }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ use core_graphics::{
|
||||
base::CGError,
|
||||
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
||||
};
|
||||
use objc;
|
||||
|
||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
||||
|
||||
@@ -107,6 +106,8 @@ pub const kCGCursorWindowLevelKey: NSInteger = 19;
|
||||
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(isize)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum NSWindowLevel {
|
||||
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
|
||||
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
|
||||
@@ -118,8 +119,6 @@ pub enum NSWindowLevel {
|
||||
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
||||
}
|
||||
|
||||
pub const NSStringEnumerationByComposedCharacterSequences: NSUInteger = 2;
|
||||
|
||||
pub type CGDisplayFadeInterval = f32;
|
||||
pub type CGDisplayReservationInterval = f32;
|
||||
pub type CGDisplayBlendFraction = f32;
|
||||
@@ -165,14 +164,14 @@ pub const IO8BitOverlayPixels: &str = "O8";
|
||||
pub type CGWindowLevel = i32;
|
||||
pub type CGDisplayModeRef = *mut c_void;
|
||||
|
||||
#[cfg_attr(
|
||||
not(use_colorsync_cgdisplaycreateuuidfromdisplayid),
|
||||
link(name = "CoreGraphics", kind = "framework")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
use_colorsync_cgdisplaycreateuuidfromdisplayid,
|
||||
link(name = "ColorSync", kind = "framework")
|
||||
)]
|
||||
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
|
||||
// However, that framework was only introduced "publicly" in macOS 10.13.
|
||||
//
|
||||
// Since we want to support older versions, we can't link to `ColorSync`
|
||||
// directly. Fortunately, it has always been available as a subframework of
|
||||
// `ApplicationServices`, see:
|
||||
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
@@ -222,3 +221,45 @@ extern "C" {
|
||||
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
|
||||
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
|
||||
}
|
||||
|
||||
mod core_video {
|
||||
use super::*;
|
||||
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
extern "C" {}
|
||||
|
||||
// CVBase.h
|
||||
|
||||
pub type CVTimeFlags = i32; // int32_t
|
||||
pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CVTime {
|
||||
pub time_value: i64, // int64_t
|
||||
pub time_scale: i32, // int32_t
|
||||
pub flags: i32, // int32_t
|
||||
}
|
||||
|
||||
// CVReturn.h
|
||||
|
||||
pub type CVReturn = i32; // int32_t
|
||||
pub const kCVReturnSuccess: CVReturn = 0;
|
||||
|
||||
// CVDisplayLink.h
|
||||
|
||||
pub type CVDisplayLinkRef = *mut c_void;
|
||||
|
||||
extern "C" {
|
||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
||||
displayID: CGDirectDisplayID,
|
||||
displayLinkOut: *mut CVDisplayLinkRef,
|
||||
) -> CVReturn;
|
||||
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(
|
||||
displayLink: CVDisplayLinkRef,
|
||||
) -> CVTime;
|
||||
pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef);
|
||||
}
|
||||
}
|
||||
|
||||
pub use core_video::*;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
@@ -9,18 +13,19 @@ mod ffi;
|
||||
mod menu;
|
||||
mod monitor;
|
||||
mod observer;
|
||||
mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
mod window_delegate;
|
||||
|
||||
use std::{fmt, ops::Deref, sync::Arc};
|
||||
|
||||
pub use self::{
|
||||
app_delegate::{get_aux_state_mut, AuxDelegateState},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
||||
pub(crate) use self::{
|
||||
app_delegate::get_aux_state_mut,
|
||||
event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, UnownedWindow, WindowId},
|
||||
};
|
||||
use crate::{
|
||||
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
|
||||
@@ -65,7 +70,7 @@ impl Deref for Window {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T: 'static>(
|
||||
pub(crate) fn new<T: 'static>(
|
||||
_window_target: &EventLoopWindowTarget<T>,
|
||||
attributes: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user