mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaaf08972d | ||
|
|
ba4660dade | ||
|
|
090800e6a6 | ||
|
|
70fc8f66e9 | ||
|
|
7601a1506d | ||
|
|
b1dad450ee | ||
|
|
8d66df7f6f | ||
|
|
1c5fcf3309 | ||
|
|
58c89c1ffc | ||
|
|
2dae807c4f | ||
|
|
8b6c8ef323 | ||
|
|
2e63493776 | ||
|
|
d2acea95cc | ||
|
|
454dc56a6e | ||
|
|
016fd47d0d | ||
|
|
8ce5f6ea41 | ||
|
|
64084c5cf0 | ||
|
|
96d29ab26c | ||
|
|
3e42fa364c | ||
|
|
cb855b87cc | ||
|
|
9c033ce101 | ||
|
|
54ad02e4b9 | ||
|
|
00fe65630e | ||
|
|
961b675d34 | ||
|
|
bcd2fba4a0 | ||
|
|
0135fe51ae | ||
|
|
ed600e415a | ||
|
|
ad892c6949 | ||
|
|
a6b25643ad | ||
|
|
bc5b6ff8ce | ||
|
|
ba6254bc25 | ||
|
|
bd2d2760f0 | ||
|
|
3de08204d3 | ||
|
|
c9030f06c0 | ||
|
|
3035546b17 | ||
|
|
57cb3126d6 | ||
|
|
221b2e71cd | ||
|
|
0dc376a9ef | ||
|
|
85052c09bb | ||
|
|
be63581654 | ||
|
|
45b5f3b031 | ||
|
|
0270516067 | ||
|
|
a90cd1c9ad | ||
|
|
dbeeaeffd9 | ||
|
|
c4bfbbe417 | ||
|
|
978ec7dfec | ||
|
|
87f44ecffb | ||
|
|
da82971f52 | ||
|
|
324dd5fa86 | ||
|
|
fdedda38d2 | ||
|
|
cf0a533461 | ||
|
|
017ff26e7d | ||
|
|
6eb79f04c8 | ||
|
|
2bf12c74dc | ||
|
|
2998bbf7db | ||
|
|
3f82a6a90d | ||
|
|
2e610111b0 | ||
|
|
63d52aae32 | ||
|
|
5b4f97edac | ||
|
|
9135eb4024 | ||
|
|
23b3c127fd | ||
|
|
b343f45500 | ||
|
|
572d61f9ba | ||
|
|
87fc19826b | ||
|
|
11d1b7a980 | ||
|
|
5ca810ba8f | ||
|
|
2d1607b3f7 | ||
|
|
a32e232020 | ||
|
|
9b03bb7276 | ||
|
|
e39596151c | ||
|
|
5289b4f206 | ||
|
|
380dc4c451 | ||
|
|
6fbdbce6dd | ||
|
|
cafcaa2cdc | ||
|
|
e00204e626 | ||
|
|
a5b89bfe5a | ||
|
|
44052a093e | ||
|
|
40cee238e2 | ||
|
|
3dc5c42387 | ||
|
|
8c4a6ddcb4 | ||
|
|
5011a67f6d | ||
|
|
d621ab5018 | ||
|
|
966c033a6c | ||
|
|
1681410ca8 | ||
|
|
a82327c73f | ||
|
|
e71f765dea | ||
|
|
0738528931 | ||
|
|
8119c72d64 | ||
|
|
43f29f0481 | ||
|
|
266219f27f | ||
|
|
7449534ba2 | ||
|
|
7aa202b872 | ||
|
|
06cec065d4 | ||
|
|
f968e64ac8 | ||
|
|
a97309690e | ||
|
|
dec45ce0ff | ||
|
|
f709ac667f | ||
|
|
ecbe04caa7 | ||
|
|
7103514ae8 | ||
|
|
8b5aa33a88 | ||
|
|
7a3b486965 | ||
|
|
fc9c78cb56 | ||
|
|
525219716c | ||
|
|
7f851fe433 | ||
|
|
5dea2a4734 | ||
|
|
821fc63a9c | ||
|
|
8e9a3d2dd3 | ||
|
|
70a77b8534 | ||
|
|
a5bb6d67f7 | ||
|
|
33a2e4cebd | ||
|
|
0ee26986d8 | ||
|
|
ec41dddd0d | ||
|
|
7a872903a4 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Generate lockfile
|
||||
# Also updates the crates.io index
|
||||
run: cargo generate-lockfile
|
||||
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "deps/apk-builder"]
|
||||
path = deps/apk-builder
|
||||
url = https://github.com/rust-windowing/android-rs-glue
|
||||
118
CHANGELOG.md
118
CHANGELOG.md
@@ -11,6 +11,124 @@ Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.29.14
|
||||
|
||||
- On X11/Wayland, fix `text` and `text_with_all_modifiers` not being `None` during compose.
|
||||
- On Wayland, don't reapply cursor grab when unchanged.
|
||||
- On X11, fix a bug where some mouse events would be unexpectedly filtered out.
|
||||
|
||||
# 0.29.13
|
||||
|
||||
- On Web, fix possible crash with `ControlFlow::Wait` and `ControlFlow::WaitUntil`.
|
||||
|
||||
# 0.29.12
|
||||
|
||||
- On X11, fix use after free during xinput2 handling.
|
||||
- On X11, filter close to zero values in mouse device events
|
||||
|
||||
# 0.29.11
|
||||
|
||||
- On Wayland, fix DeviceEvent::Motion not being sent
|
||||
- On X11, don't require XIM to run.
|
||||
- On X11, fix xkb state not being updated correctly sometimes leading to wrong input.
|
||||
- Fix compatibility with 32-bit platforms without 64-bit atomics.
|
||||
- On macOS, fix incorrect IME cursor rect origin.
|
||||
- On X11, fix swapped instance and general class names.
|
||||
- On Windows, fixed a race condition when sending an event through the loop proxy.
|
||||
- On Wayland, disable `Occluded` event handling.
|
||||
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
|
||||
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
|
||||
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
|
||||
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
|
||||
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
|
||||
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
|
||||
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`.
|
||||
- On Orbital, implement `KeyEventExtModifierSupplement`.
|
||||
- On Orbital, map keys to `NamedKey` when possible.
|
||||
- On Orbital, implement `set_cursor_grab`.
|
||||
- On Orbital, implement `set_cursor_visible`.
|
||||
- On Orbital, implement `drag_window`.
|
||||
- On Orbital, implement `drag_resize_window`.
|
||||
- On Orbital, implement `set_transparent`.
|
||||
- On Orbital, implement `set_visible`.
|
||||
- On Orbital, implement `is_visible`.
|
||||
- On Orbital, implement `set_resizable`.
|
||||
- On Orbital, implement `is_resizable`.
|
||||
- On Orbital, implement `set_maximized`.
|
||||
- On Orbital, implement `is_maximized`.
|
||||
- On Orbital, implement `set_decorations`.
|
||||
- On Orbital, implement `is_decorated`.
|
||||
- On Orbital, implement `set_window_level`.
|
||||
- On Orbital, emit `DeviceEvent::MouseMotion`.
|
||||
- On Wayland, fix title in CSD not updated from `AboutToWait`.
|
||||
|
||||
# 0.29.10
|
||||
|
||||
- On Web, account for canvas being focused already before event loop starts.
|
||||
- On Web, increase cursor position accuracy.
|
||||
|
||||
# 0.29.9
|
||||
|
||||
- On X11, fix `NotSupported` error not propagated when creating event loop.
|
||||
- On Wayland, fix resize not issued when scale changes
|
||||
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
|
||||
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
|
||||
|
||||
# 0.29.8
|
||||
|
||||
- On X11, fix IME input lagging behind.
|
||||
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
|
||||
- On X11, fix keymap not updated from xmodmap.
|
||||
- On X11, reduce the amount of time spent fetching screen resources.
|
||||
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
|
||||
- On Wayland, fix `Window::inner_size` not using the correct rounding.
|
||||
|
||||
# 0.29.7
|
||||
|
||||
- On X11, fix `Xft.dpi` reload during runtime.
|
||||
- On X11, fix window minimize.
|
||||
|
||||
# 0.29.6
|
||||
|
||||
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
|
||||
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
|
||||
|
||||
# 0.29.5
|
||||
|
||||
- On macOS, remove spurious error logging when handling `Fn`.
|
||||
- On X11, fix an issue where floating point data from the server is
|
||||
misinterpreted during a drag and drop operation.
|
||||
- On X11, fix a bug where focusing the window would panic.
|
||||
- On macOS, fix `refresh_rate_millihertz`.
|
||||
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
||||
- On X11, fix `Xft.dpi` detection from Xresources.
|
||||
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
|
||||
- On Wayland, fix resize being sent on focus change.
|
||||
- On Windows, fix `set_ime_cursor_area`.
|
||||
|
||||
# 0.29.4
|
||||
|
||||
- Fix crash when running iOS app on macOS.
|
||||
- On X11, check common alternative cursor names when loading cursor.
|
||||
- On X11, reload the DPI after a property change event.
|
||||
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
|
||||
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
|
||||
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
|
||||
- On Wayland, fix `wl_surface` being destroyed before associated objects.
|
||||
- On macOS, fix assertion when pressing `Fn` key.
|
||||
|
||||
# 0.29.3
|
||||
|
||||
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
|
||||
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
|
||||
- On Wayland, ignore resize requests when the window is fully tiled.
|
||||
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
|
||||
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
|
||||
- On Windows, add support for `Window::set_transparent`.
|
||||
- On macOS, fix deadlock when entering a nested event loop from an event handler.
|
||||
- On macOS, add support for `Window::set_blur`.
|
||||
|
||||
# 0.29.2
|
||||
|
||||
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
|
||||
|
||||
@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
|
||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
||||
your description of the issue as detailed as possible:
|
||||
|
||||
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
|
||||
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
|
||||
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
||||
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
||||
should be supported by winit
|
||||
@@ -21,7 +21,7 @@ your description of the issue as detailed as possible:
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
||||
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
|
||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||
- you updated any relevant documentation in winit
|
||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
||||
@@ -34,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
|
||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||
should be updated.
|
||||
|
||||
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
|
||||
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
|
||||
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).
|
||||
|
||||
@@ -46,27 +46,26 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
|
||||
|
||||
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
|
||||
|
||||
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
|
||||
## Release process
|
||||
|
||||
Given that winit is a widely used library we should be able to make a patch
|
||||
Given that winit is a widely used library, we should be able to make a patch
|
||||
releases at any time we want without blocking the development of new features.
|
||||
|
||||
To achieve these goals, a new branch is created for every new release. Releases
|
||||
and later patch releases are committed and tagged in this branch.
|
||||
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
|
||||
|
||||
The exact steps for an exemplary `0.2.0` release might look like this:
|
||||
1. Initially the version on the latest master is `0.1.0`
|
||||
1. Initially, the version on the latest master is `0.1.0`
|
||||
2. A new `v0.2.x` branch is created for the release
|
||||
3. In the branch, the version is bumped to `v0.2.0`
|
||||
4. The new commit in the branch is tagged `v0.2.0`
|
||||
5. The version is pushed to crates.io
|
||||
6. A GitHub release is created for the `v0.2.0` tag
|
||||
7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
|
||||
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
|
||||
|
||||
When doing a patch release the process is similar:
|
||||
1. Initially the version of the latest release is `0.2.0`
|
||||
When doing a patch release, the process is similar:
|
||||
1. Initially, the version of the latest release is `0.2.0`
|
||||
2. Checkout the `v0.2.x` branch
|
||||
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
|
||||
4. Follow steps 3-7 of the regular release example
|
||||
|
||||
25
Cargo.toml
25
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.2"
|
||||
version = "0.29.14"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -13,7 +13,14 @@ categories = ["gui"]
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["rwh_04", "rwh_05", "rwh_06", "serde"]
|
||||
features = [
|
||||
"rwh_04",
|
||||
"rwh_05",
|
||||
"rwh_06",
|
||||
"serde",
|
||||
# Enabled to get docs to compile
|
||||
"android-native-activity",
|
||||
]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
# These are all tested in CI
|
||||
targets = [
|
||||
@@ -54,12 +61,12 @@ cfg_aliases = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
cursor-icon = "1.0.0"
|
||||
cursor-icon = "1.1.0"
|
||||
log = "0.4"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
once_cell = "1.12"
|
||||
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
|
||||
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
|
||||
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
|
||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
smol_str = "0.2.0"
|
||||
@@ -74,7 +81,7 @@ softbuffer = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android-activity = "0.5.0"
|
||||
ndk = "0.8.0"
|
||||
ndk = { version = "0.8.0", default-features = false }
|
||||
ndk-sys = "0.5.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
@@ -153,14 +160,14 @@ memmap2 = { version = "0.9.0", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true }
|
||||
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-client = { version = "0.31.1", optional = true }
|
||||
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
x11-dl = { version = "2.19.1", optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.2"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
orbclient = { version = "0.3.42", default-features = false }
|
||||
|
||||
16
FEATURES.md
16
FEATURES.md
@@ -1,6 +1,6 @@
|
||||
# Winit Scope
|
||||
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling, and can
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling and can
|
||||
be used to create both games and applications. It supports the following main graphical platforms:
|
||||
- Desktop
|
||||
- Windows 7+ (10+ is tested regularly)
|
||||
@@ -45,10 +45,10 @@ be released and the library will enter maintenance mode. For the most part, new
|
||||
be added past this point. New platform features may be accepted and exposed through point releases.
|
||||
|
||||
### Tier upgrades
|
||||
Some platform features could in theory be exposed across multiple platforms, but have not gone
|
||||
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
|
||||
through the implementation work necessary to function on all platforms. When one of these features
|
||||
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
||||
If that gets accepted, the platform-specific functions gets deprecated and become permanently
|
||||
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
||||
exposed through the core, cross-platform API.
|
||||
|
||||
# Features
|
||||
@@ -88,7 +88,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
||||
creation.
|
||||
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
||||
for fullscreen windows, and if applicable, captures the monitor for exclusive
|
||||
for fullscreen windows and, if applicable, captures the monitor for exclusive
|
||||
use by this application.
|
||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
||||
@@ -105,7 +105,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **Mouse set location**: Forcibly changing the location of the pointer.
|
||||
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
||||
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
||||
- **Cursor 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.
|
||||
@@ -151,12 +151,12 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* Valid orientations
|
||||
* Home indicator visibility
|
||||
* Status bar visibility and style
|
||||
* Deferrring system gestures
|
||||
* Deferring system gestures
|
||||
* Getting the device idiom
|
||||
* Getting the preferred video mode
|
||||
|
||||
### Web
|
||||
* Get if systems preferred color scheme is "dark"
|
||||
* Get if the systems preferred color scheme is "dark"
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
@@ -166,7 +166,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
Legend:
|
||||
|
||||
- ✔️: Works as intended
|
||||
- ▢: Mostly works but some bugs are known
|
||||
- ▢: Mostly works, but some bugs are known
|
||||
- ❌: Missing feature or large bugs making it unusable
|
||||
- **N/A**: Not applicable for this platform
|
||||
- ❓: Unknown status
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
The winit maintainers would like to recognize the following former winit
|
||||
contributors, without whom winit would not exist in its current form. We thank
|
||||
them deeply for their time and efforts, and wish them best of luck in their
|
||||
them deeply for their time and efforts and wish them the best of luck in their
|
||||
future endeavors:
|
||||
|
||||
* [@tomaka]: For creating the winit project and guiding it through its early
|
||||
years of existence.
|
||||
* [@vberger]: For diligently creating the Wayland backend, and being its
|
||||
* [@vberger]: For diligently creating the Wayland backend and being its
|
||||
extremely helpful and benevolent maintainer for years.
|
||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||
winit backend, and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
|
||||
winit backend and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a
|
||||
vastly more sustainable era of winit.
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
|
||||
stellar dedication to improving both winit and glutin.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend, and his
|
||||
immense involvement in designing and implementing the new keyboard API.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API.
|
||||
|
||||
[@tomaka]: https://github.com/tomaka
|
||||
[@vberger]: https://github.com/vberger
|
||||
|
||||
33
README.md
33
README.md
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.2"
|
||||
winit = "0.29.14"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -26,7 +26,7 @@ Join us in any of these:
|
||||
|
||||
Winit is a window creation and management library. It can create windows and lets you handle
|
||||
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
|
||||
produced by window.
|
||||
produced by the window.
|
||||
|
||||
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
|
||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||
@@ -42,7 +42,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
|
||||
the MSRV will be accompanied by a minor version bump.
|
||||
|
||||
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||
@@ -53,12 +53,11 @@ min(sid, stable - 3)
|
||||
```
|
||||
|
||||
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
|
||||
`stable` is the latest stable version of Rust. This bound may be broken in the
|
||||
event of a major ecosystem shift or a security vulnerability.
|
||||
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
|
||||
|
||||
[Debian Sid]: https://packages.debian.org/sid/rustc
|
||||
|
||||
The exception to this is for the Android platform, where a higher Rust version
|
||||
The exception is for the Android platform, where a higher Rust version
|
||||
must be used for certain Android features. In this case, the MSRV will be
|
||||
capped at the latest stable version of Rust minus three. This inconsistency is
|
||||
not reflected in Cargo metadata, as it is not powerful enough to expose this
|
||||
@@ -86,7 +85,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
|
||||
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||
insert it into the DOM yourself.
|
||||
|
||||
For example code using Winit with WebAssembly, check out the [web example]. For
|
||||
For the example code using Winit with WebAssembly, check out the [web example]. For
|
||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||
book].
|
||||
|
||||
@@ -109,14 +108,14 @@ glue crate (prior to `0.28` it used
|
||||
|
||||
The version of the glue crate that your application depends on _must_ match the
|
||||
version that Winit depends on because the glue crate is responsible for your
|
||||
application's main entrypoint. If Cargo resolves multiple versions they will
|
||||
application's main entry point. If Cargo resolves multiple versions, they will
|
||||
clash.
|
||||
|
||||
`winit` glue compatibility table:
|
||||
|
||||
| winit | ndk-glue |
|
||||
| :---: | :--------------------------: |
|
||||
| 0.29.2| `android-activity = "0.5"` |
|
||||
| 0.29 | `android-activity = "0.5"` |
|
||||
| 0.28 | `android-activity = "0.4"` |
|
||||
| 0.27 | `ndk-glue = "0.7"` |
|
||||
| 0.26 | `ndk-glue = "0.5"` |
|
||||
@@ -127,7 +126,7 @@ The recommended way to avoid a conflict with the glue version is to avoid explic
|
||||
depending on the `android-activity` crate, and instead consume the API that
|
||||
is re-exported by Winit under `winit::platform::android::activity::*`
|
||||
|
||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
||||
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
|
||||
|
||||
```toml
|
||||
[lib]
|
||||
@@ -135,14 +134,14 @@ name = "main"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
All Android applications are based on an `Activity` subclass and the
|
||||
All Android applications are based on an `Activity` subclass, and the
|
||||
`android-activity` crate is designed to support different choices for this base
|
||||
class. Your application _must_ specify the base class it needs via a feature flag:
|
||||
|
||||
| Base Class | Feature Flag | Notes |
|
||||
| :--------------: | :---------------: | :-----: |
|
||||
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
|
||||
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
|
||||
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
|
||||
@@ -151,13 +150,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
|
||||
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
|
||||
[Gradle]: https://developer.android.com/studio/build
|
||||
|
||||
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
|
||||
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
|
||||
|
||||
##### Converting from `ndk-glue` to `android-activity`
|
||||
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
|
||||
1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.2", features = [ "android-native-activity" ] }`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.14", features = [ "android-native-activity" ] }`
|
||||
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
|
||||
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||
|
||||
@@ -173,7 +172,7 @@ If you encounter problems, you should try doing your initialization inside
|
||||
#### iOS
|
||||
|
||||
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
|
||||
by all UI related code, see issue [#1705]. You should consider creating your windows
|
||||
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
|
||||
inside `Event::Resumed`.
|
||||
|
||||
|
||||
@@ -184,5 +183,5 @@ inside `Event::Resumed`.
|
||||
|
||||
#### Redox OS
|
||||
|
||||
Redox OS has some functionality not present yet, that will be implemented when
|
||||
Redox OS has some functionality not yet present that will be implemented when
|
||||
its orbital display server provides it.
|
||||
|
||||
@@ -5,7 +5,7 @@ These images are used in the documentation of `winit`.
|
||||
## keyboard_*.svg
|
||||
|
||||
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
|
||||
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
|
||||
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
|
||||
which have been released under the same license as a derivative work.
|
||||
|
||||
56
examples/focus.rs
Normal file
56
examples/focus.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
//! Example for focusing a window.
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time as time;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut deadline = time::Instant::now() + time::Duration::from_secs(3);
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
// Timeout reached; focus the window.
|
||||
println!("Re-focusing the window.");
|
||||
deadline += time::Duration::from_secs(3);
|
||||
window.focus_window();
|
||||
}
|
||||
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline));
|
||||
})
|
||||
}
|
||||
@@ -7,17 +7,30 @@
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
use winit::window::Window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
pub use platform::fill_window;
|
||||
|
||||
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
||||
pub(super) fn fill_window(window: &Window) {
|
||||
use softbuffer::{Context, Surface};
|
||||
mod platform {
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use winit::window::Window;
|
||||
use winit::window::WindowId;
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
|
||||
}
|
||||
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
@@ -35,52 +48,69 @@ pub(super) fn fill_window(window: &Window) {
|
||||
}
|
||||
}
|
||||
|
||||
fn surface(&mut self, w: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(w.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, w) }
|
||||
fn create_surface(&mut self, window: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, window) }
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
||||
pub fn fill_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.create_surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
|
||||
surface
|
||||
.resize(width, height)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
GC.with(|gc| {
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
let size = window.inner_size();
|
||||
|
||||
surface
|
||||
.resize(
|
||||
NonZeroU32::new(size.width).expect("Width must be greater than zero"),
|
||||
NonZeroU32::new(size.height).expect("Height must be greater than zero"),
|
||||
)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let mut gc = gc.borrow_mut();
|
||||
if let Some(context) = gc.as_mut() {
|
||||
context.destroy_surface(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
||||
pub(super) fn fill_window(_window: &Window) {
|
||||
// No-op on mobile platforms.
|
||||
mod platform {
|
||||
pub fn fill_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ pub fn main() {
|
||||
#[cfg(wasm_platform)]
|
||||
mod wasm {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
use winit::{
|
||||
dpi::PhysicalSize,
|
||||
|
||||
@@ -40,6 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
window_id,
|
||||
} if window.id() == window_id => {
|
||||
println!("--------------------------------------------------------- Window {idx} CloseRequested");
|
||||
fill::cleanup_window(window);
|
||||
app.window = None;
|
||||
}
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
|
||||
39
src/dpi.rs
39
src/dpi.rs
@@ -4,13 +4,13 @@
|
||||
//!
|
||||
//! Modern computer screens don't have a consistent relationship between resolution and size.
|
||||
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
|
||||
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
|
||||
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
|
||||
//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
|
||||
//! desktop nor mobile screens have consistent resolutions within their own size classes - common
|
||||
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
|
||||
//! and beyond.
|
||||
//!
|
||||
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
|
||||
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
|
||||
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
|
||||
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
|
||||
@@ -25,12 +25,12 @@
|
||||
//!
|
||||
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
|
||||
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
|
||||
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
|
||||
//! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
|
||||
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
|
||||
//!
|
||||
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
|
||||
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
|
||||
//! usually a mistake since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
|
||||
//! than any DPI-dependent units.
|
||||
//!
|
||||
//! ### Position and Size types
|
||||
@@ -42,11 +42,11 @@
|
||||
//! coordinates as input, allowing you to use the most convenient coordinate system for your
|
||||
//! particular application.
|
||||
//!
|
||||
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
|
||||
//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the
|
||||
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
||||
//! 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
|
||||
//! 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
|
||||
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
|
||||
//! int, although rounding lessens the problem.
|
||||
@@ -55,34 +55,35 @@
|
||||
//!
|
||||
//! 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
|
||||
//! monitor or if the user changes their DPI settings. This allows you 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, its scale factor
|
||||
//! can be found by calling [`window.scale_factor()`].
|
||||
//!
|
||||
//! ## How is the scale factor calculated?
|
||||
//!
|
||||
//! Scale factor is calculated differently on different platforms:
|
||||
//! The scale factor is calculated differently on different platforms:
|
||||
//!
|
||||
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
|
||||
//! display settings. While users are free to select any option they want, they're only given a
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
|
||||
//! global and changing it requires logging out. See [this article][windows_1] for technical
|
||||
//! details.
|
||||
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
|
||||
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
|
||||
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
|
||||
//! the specific value varies across devices.
|
||||
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
|
||||
//! displays. When available, the user may pick a per-monitor scaling factor from a set of
|
||||
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
|
||||
//! but the specific value varies across devices.
|
||||
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
|
||||
//! currently uses a three-pronged approach:
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
||||
//! + 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, 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
|
||||
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
|
||||
//! - **Wayland:** Scale factor is suggested by the the compositor.
|
||||
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
|
||||
//! monitor scale factor may differ from the window scale factor.
|
||||
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
|
||||
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
|
||||
//! information.
|
||||
|
||||
@@ -574,7 +574,7 @@ pub enum WindowEvent {
|
||||
/// ### Others
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
/// - **Android / Windows / Orbital:** Unsupported.
|
||||
/// - **Android / Wayland / Windows / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::{error, fmt};
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
@@ -224,14 +224,22 @@ impl<T> EventLoop<T> {
|
||||
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
|
||||
/// and any values not passed to this function will *not* be dropped.
|
||||
///
|
||||
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
|
||||
/// Web applications are recommended to use
|
||||
#[cfg_attr(
|
||||
wasm_platform,
|
||||
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
|
||||
)]
|
||||
#[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
|
||||
/// [^1] instead of [`run()`] to avoid the need
|
||||
/// for the Javascript exception trick, and to make it clearer that the event loop runs
|
||||
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
|
||||
/// current thread of execution like it does on other platforms.
|
||||
///
|
||||
/// This function won't be available with `target_feature = "exception-handling"`.
|
||||
///
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
/// [`run()`]: Self::run()
|
||||
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
|
||||
#[inline]
|
||||
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
|
||||
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
|
||||
@@ -451,16 +459,16 @@ pub enum DeviceEvents {
|
||||
/// executed and removed from the list.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AsyncRequestSerial {
|
||||
serial: u64,
|
||||
serial: usize,
|
||||
}
|
||||
|
||||
impl AsyncRequestSerial {
|
||||
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
|
||||
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn get() -> Self {
|
||||
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||
// NOTE: we rely on wrap around here, while the user may just request
|
||||
// in the loop u64::MAX times that's issue is considered on them.
|
||||
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
|
||||
// NOTE: We rely on wrap around here, while the user may just request
|
||||
// in the loop usize::MAX times that's issue is considered on them.
|
||||
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
|
||||
Self { serial }
|
||||
}
|
||||
|
||||
@@ -49,11 +49,7 @@ impl fmt::Display for BadIcon {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl Error for BadIcon {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct RgbaIcon {
|
||||
|
||||
66
src/lib.rs
66
src/lib.rs
@@ -10,12 +10,12 @@
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Once this is done there are two ways to create a [`Window`]:
|
||||
//! Once this is done, there are two ways to create a [`Window`]:
|
||||
//!
|
||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//!
|
||||
//! The first method is the simplest, and will give you default values for everything. The second
|
||||
//! The first method is the simplest and will give you default values for everything. The second
|
||||
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
|
||||
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
|
||||
//!
|
||||
@@ -26,17 +26,37 @@
|
||||
//! window or a key getting pressed while the window is focused. Devices can generate
|
||||
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
|
||||
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
|
||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
|
||||
//!
|
||||
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
|
||||
//! You can retrieve events by calling [`EventLoop::run()`]. This function will
|
||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
||||
//! will run until [`exit()`] is used, at which point [`Event`]`::`[`LoopExiting`].
|
||||
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
|
||||
//!
|
||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
|
||||
//! most other platforms. However, this model can be re-implemented to an extent with
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged, beyond compatibility reasons.
|
||||
#![cfg_attr(
|
||||
any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
),
|
||||
doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
not(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
android_platform,
|
||||
x11_platform,
|
||||
wayland_platform
|
||||
)),
|
||||
doc = "`EventLoopExtPumpEvents::pump_events()`"
|
||||
)]
|
||||
//! [^1]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged beyond compatibility reasons.
|
||||
//!
|
||||
//!
|
||||
//! ```no_run
|
||||
@@ -72,9 +92,9 @@
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! //
|
||||
//! // You only need to call this if you've determined that you need to redraw, in
|
||||
//! // You only need to call this if you've determined that you need to redraw in
|
||||
//! // applications which do not always need to. Applications that redraw continuously
|
||||
//! // can just render here instead.
|
||||
//! // can render here instead.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::WindowEvent {
|
||||
@@ -92,16 +112,16 @@
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
|
||||
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
||||
//! compared to the value returned by [`Window::id()`] to determine which [`Window`]
|
||||
//! dispatched the event.
|
||||
//!
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
|
||||
//! 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.
|
||||
//! 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
|
||||
@@ -110,9 +130,8 @@
|
||||
//! window visible only once you're ready to render into it.
|
||||
//!
|
||||
//! [`EventLoop`]: event_loop::EventLoop
|
||||
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
|
||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
||||
//! [event_loop_run]: event_loop::EventLoop::run
|
||||
//! [`EventLoop::run()`]: event_loop::EventLoop::run
|
||||
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
@@ -120,15 +139,14 @@
|
||||
//! [window_new]: window::Window::new
|
||||
//! [window_builder_new]: window::WindowBuilder::new
|
||||
//! [window_builder_build]: window::WindowBuilder::build
|
||||
//! [window_id_fn]: window::Window::id
|
||||
//! [`Event`]: event::Event
|
||||
//! [`Window::id()`]: window::Window::id
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
//! [`DeviceEvent`]: event::DeviceEvent
|
||||
//! [`UserEvent`]: event::Event::UserEvent
|
||||
//! [`LoopExiting`]: event::Event::LoopExiting
|
||||
//! [`platform`]: platform
|
||||
//! [`Event::UserEvent`]: event::Event::UserEvent
|
||||
//! [`Event::LoopExiting`]: event::Event::LoopExiting
|
||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||
//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland.
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
@@ -165,13 +183,13 @@ pub mod window;
|
||||
pub mod platform;
|
||||
|
||||
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
|
||||
/// and `Sync`, since they always excute on a single thread.
|
||||
/// and `Sync`, since they always execute on a single thread.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Winit can run only one event loop at the time and the event loop itself is tied to some thread.
|
||||
/// The objects could be send across the threads, but once passed to winit, they execute on the
|
||||
/// mean thread if the platform demands it. Thus marking such objects as `Send + Sync` is safe.
|
||||
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
|
||||
/// The objects could be sent across the threads, but once passed to winit, they execute on the
|
||||
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
|
||||
|
||||
@@ -138,14 +138,18 @@ impl MonitorHandle {
|
||||
self.inner.refresh_rate_millihertz()
|
||||
}
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
|
||||
/// pixels and vice versa, use [`Window::scale_factor`].
|
||||
///
|
||||
/// See the [`dpi`](crate::dpi) module for more information.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
|
||||
/// - **Wayland:** May differ from [`Window::scale_factor`].
|
||||
/// - **Android:** Always returns 1.0.
|
||||
///
|
||||
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.inner.scale_factor()
|
||||
|
||||
@@ -84,5 +84,10 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
|
||||
/// use winit::platform::android::activity::AndroidApp;
|
||||
/// ```
|
||||
pub mod activity {
|
||||
// We enable the `"native-activity"` feature just so that we can build the
|
||||
// docs, but it'll be very confusing for users to see the docs with that
|
||||
// feature enabled, so we avoid inlining it so that they're forced to view
|
||||
// it on the crate's own docs.rs page.
|
||||
#[doc(no_inline)]
|
||||
pub use android_activity::*;
|
||||
}
|
||||
|
||||
@@ -53,5 +53,13 @@ pub mod run_on_demand;
|
||||
))]
|
||||
pub mod pump_events;
|
||||
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
x11_platform,
|
||||
wayland_platform,
|
||||
orbital_platform,
|
||||
docsrs
|
||||
))]
|
||||
pub mod modifier_supplement;
|
||||
pub mod scancode;
|
||||
|
||||
@@ -35,9 +35,9 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
|
||||
///
|
||||
/// # Caveats
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to
|
||||
/// return to the caller (specifically this is impossible on iOS and Web - though with
|
||||
/// the Web backend it is possible to use `spawn()` more than once instead).
|
||||
/// - This extension isn't available on all platforms, since it's not always possible to return
|
||||
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
|
||||
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
|
||||
/// - No [`Window`] state can be carried between separate runs of the event loop.
|
||||
///
|
||||
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need
|
||||
@@ -57,8 +57,13 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// on an event loop that is internal to the browser itself.
|
||||
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
|
||||
///
|
||||
/// [`exit()`]: EventLoopWindowTarget::exit
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
|
||||
#[cfg_attr(
|
||||
not(wasm_platform),
|
||||
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
|
||||
)]
|
||||
///
|
||||
/// [`exit()`]: EventLoopWindowTarget::exit()
|
||||
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
|
||||
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
@@ -71,6 +76,14 @@ impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
{
|
||||
self.event_loop.window_target().clear_exit();
|
||||
self.event_loop.run_on_demand(event_handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
/// Clear exit status.
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.p.clear_exit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +109,28 @@ pub trait EventLoopExtWebSys {
|
||||
|
||||
/// Initializes the winit event loop.
|
||||
///
|
||||
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its `!` return type.
|
||||
/// Unlike
|
||||
#[cfg_attr(
|
||||
all(wasm_platform, target_feature = "exception-handling"),
|
||||
doc = "`run()`"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]"
|
||||
)]
|
||||
/// [^1], this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its [`!`] return type.
|
||||
///
|
||||
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
|
||||
/// by calling this function again. This can be useful if you want to recreate the event loop
|
||||
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
|
||||
/// event loop when switching between tabs on a single page application.
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(all(wasm_platform, target_feature = "exception-handling")),
|
||||
doc = "[`run()`]: EventLoop::run()"
|
||||
)]
|
||||
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
|
||||
|
||||
@@ -96,7 +96,7 @@ pub trait WindowBuilderExtX11 {
|
||||
/// Build window with the given `general` and `instance` names.
|
||||
///
|
||||
/// 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"`.
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`.
|
||||
///
|
||||
/// 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)
|
||||
|
||||
@@ -713,6 +713,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
self.exit.set(true)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.exit.set(false)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
@@ -200,6 +200,10 @@ impl AppState {
|
||||
)
|
||||
}
|
||||
|
||||
fn has_terminated(&self) -> bool {
|
||||
matches!(self.state(), AppStateImpl::Terminated)
|
||||
}
|
||||
|
||||
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
|
||||
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
|
||||
AppStateImpl::NotLaunched {
|
||||
@@ -243,7 +247,7 @@ impl AppState {
|
||||
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
|
||||
// before `AppState::did_finish_launching` is called, pretend there is no running
|
||||
// event loop.
|
||||
if !self.has_launched() {
|
||||
if !self.has_launched() || self.has_terminated() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -390,7 +394,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
fn events_cleared_transition(&mut self) {
|
||||
if !self.has_launched() {
|
||||
if !self.has_launched() || self.has_terminated() {
|
||||
return;
|
||||
}
|
||||
let (waiting_event_handler, old) = match self.take_state() {
|
||||
@@ -586,6 +590,10 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
|
||||
events: I,
|
||||
) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
if this.has_terminated() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (mut event_handler, active_control_flow, processing_redraws) =
|
||||
match this.try_user_callback_transition() {
|
||||
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
|
||||
@@ -737,7 +745,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
|
||||
|
||||
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
if !this.has_launched() {
|
||||
if !this.has_launched() || this.has_terminated() {
|
||||
return;
|
||||
}
|
||||
match this.state_mut() {
|
||||
|
||||
@@ -289,7 +289,7 @@ fn setup_control_flow_observers() {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
kCFRunLoopExit => {} // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,7 @@ fn setup_control_flow_observers() {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||
kCFRunLoopExit => {} // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use icrate::Foundation::{NSInteger, NSUInteger};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
pub mod keymap;
|
||||
pub mod xkb_state;
|
||||
pub mod xkb;
|
||||
|
||||
124
src/platform_impl/linux/common/xkb/compose.rs
Normal file
124
src/platform_impl/linux/common/xkb/compose.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
//! XKB compose handling.
|
||||
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::XkbContext;
|
||||
use super::XKBCH;
|
||||
use smol_str::SmolStr;
|
||||
use xkbcommon_dl::{
|
||||
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
|
||||
xkb_compose_status, xkb_compose_table, xkb_keysym_t,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbComposeTable {
|
||||
table: NonNull<xkb_compose_table>,
|
||||
}
|
||||
|
||||
impl XkbComposeTable {
|
||||
pub fn new(context: &XkbContext) -> Option<Self> {
|
||||
let locale = env::var_os("LC_ALL")
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LC_CTYPE"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LANG"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.unwrap_or_else(|| "C".into());
|
||||
let locale = CString::new(locale.into_vec()).unwrap();
|
||||
|
||||
let table = unsafe {
|
||||
(XKBCH.xkb_compose_table_new_from_locale)(
|
||||
context.as_ptr(),
|
||||
locale.as_ptr(),
|
||||
xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
let table = NonNull::new(table)?;
|
||||
Some(Self { table })
|
||||
}
|
||||
|
||||
/// Create new state with the given compose table.
|
||||
pub fn new_state(&self) -> Option<XkbComposeState> {
|
||||
let state = unsafe {
|
||||
(XKBCH.xkb_compose_state_new)(
|
||||
self.table.as_ptr(),
|
||||
xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
let state = NonNull::new(state)?;
|
||||
Some(XkbComposeState { state })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbComposeTable {
|
||||
type Target = NonNull<xkb_compose_table>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.table
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbComposeTable {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_table_unref)(self.table.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbComposeState {
|
||||
state: NonNull<xkb_compose_state>,
|
||||
}
|
||||
|
||||
impl XkbComposeState {
|
||||
pub fn get_string(&mut self, scratch_buffer: &mut Vec<u8>) -> Option<SmolStr> {
|
||||
super::make_string_with(scratch_buffer, |ptr, len| unsafe {
|
||||
(XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus {
|
||||
let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) };
|
||||
match feed_result {
|
||||
xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
|
||||
xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
|
||||
ComposeStatus::Accepted(self.status())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_state_reset)(self.state.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn status(&mut self) -> xkb_compose_status {
|
||||
unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbComposeState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_state_unref)(self.state.as_ptr());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ComposeStatus {
|
||||
Accepted(xkb_compose_status),
|
||||
Ignored,
|
||||
None,
|
||||
}
|
||||
@@ -1,6 +1,24 @@
|
||||
//! Convert XKB keys to Winit keys.
|
||||
//! XKB keymap.
|
||||
|
||||
use std::ffi::c_char;
|
||||
use std::ops::Deref;
|
||||
use std::ptr::{self, NonNull};
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
use x11_dl::xlib_xcb::xcb_connection_t;
|
||||
#[cfg(wayland_platform)]
|
||||
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
|
||||
|
||||
use xkb::XKB_MOD_INVALID;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t,
|
||||
xkb_layout_index_t, xkb_mod_index_t,
|
||||
};
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform_impl::common::xkb::XKBXH;
|
||||
use crate::platform_impl::common::xkb::{XkbContext, XKBH};
|
||||
|
||||
/// Map the raw X11-style keycode to the `KeyCode` enum.
|
||||
///
|
||||
@@ -504,7 +522,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
|
||||
keysyms::KP_F4 => NamedKey::F4,
|
||||
keysyms::KP_Home => NamedKey::Home,
|
||||
keysyms::KP_Left => NamedKey::ArrowLeft,
|
||||
keysyms::KP_Up => NamedKey::ArrowLeft,
|
||||
keysyms::KP_Up => NamedKey::ArrowUp,
|
||||
keysyms::KP_Right => NamedKey::ArrowRight,
|
||||
keysyms::KP_Down => NamedKey::ArrowDown,
|
||||
// keysyms::KP_Prior => NamedKey::PageUp,
|
||||
@@ -894,3 +912,140 @@ pub fn keysym_location(keysym: u32) -> KeyLocation {
|
||||
_ => KeyLocation::Standard,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbKeymap {
|
||||
keymap: NonNull<xkb_keymap>,
|
||||
_mods_indices: ModsIndices,
|
||||
pub _core_keyboard_id: i32,
|
||||
}
|
||||
|
||||
impl XkbKeymap {
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option<Self> {
|
||||
let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? };
|
||||
|
||||
let keymap = unsafe {
|
||||
let keymap = (XKBH.xkb_keymap_new_from_string)(
|
||||
(*context).as_ptr(),
|
||||
map.as_ptr() as *const _,
|
||||
xkb::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
NonNull::new(keymap)?
|
||||
};
|
||||
|
||||
Some(Self::new_inner(keymap, 0))
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn from_x11_keymap(
|
||||
context: &XkbContext,
|
||||
xcb: *mut xcb_connection_t,
|
||||
core_keyboard_id: i32,
|
||||
) -> Option<Self> {
|
||||
let keymap = unsafe {
|
||||
(XKBXH.xkb_x11_keymap_new_from_device)(
|
||||
context.as_ptr(),
|
||||
xcb,
|
||||
core_keyboard_id,
|
||||
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
let keymap = NonNull::new(keymap)?;
|
||||
Some(Self::new_inner(keymap, core_keyboard_id))
|
||||
}
|
||||
|
||||
fn new_inner(keymap: NonNull<xkb_keymap>, _core_keyboard_id: i32) -> Self {
|
||||
let mods_indices = ModsIndices {
|
||||
shift: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_SHIFT),
|
||||
caps: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CAPS),
|
||||
ctrl: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CTRL),
|
||||
alt: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_ALT),
|
||||
num: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_NUM),
|
||||
mod3: mod_index_for_name(keymap, b"Mod3\0"),
|
||||
logo: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_LOGO),
|
||||
mod5: mod_index_for_name(keymap, b"Mod5\0"),
|
||||
};
|
||||
|
||||
Self {
|
||||
keymap,
|
||||
_mods_indices: mods_indices,
|
||||
_core_keyboard_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn mods_indices(&self) -> ModsIndices {
|
||||
self._mods_indices
|
||||
}
|
||||
|
||||
pub fn first_keysym_by_level(
|
||||
&mut self,
|
||||
layout: xkb_layout_index_t,
|
||||
keycode: xkb_keycode_t,
|
||||
) -> xkb_keysym_t {
|
||||
unsafe {
|
||||
let mut keysyms = ptr::null();
|
||||
let count = (XKBH.xkb_keymap_key_get_syms_by_level)(
|
||||
self.keymap.as_ptr(),
|
||||
keycode,
|
||||
layout,
|
||||
// NOTE: The level should be zero to ignore modifiers.
|
||||
0,
|
||||
&mut keysyms,
|
||||
);
|
||||
|
||||
if count == 1 {
|
||||
*keysyms
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the given key repeats.
|
||||
pub fn key_repeats(&mut self, keycode: xkb_keycode_t) -> bool {
|
||||
unsafe { (XKBH.xkb_keymap_key_repeats)(self.keymap.as_ptr(), keycode) == 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbKeymap {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_keymap_unref)(self.keymap.as_ptr());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbKeymap {
|
||||
type Target = NonNull<xkb_keymap>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.keymap
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifier index in the keymap.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct ModsIndices {
|
||||
pub shift: Option<xkb_mod_index_t>,
|
||||
pub caps: Option<xkb_mod_index_t>,
|
||||
pub ctrl: Option<xkb_mod_index_t>,
|
||||
pub alt: Option<xkb_mod_index_t>,
|
||||
pub num: Option<xkb_mod_index_t>,
|
||||
pub mod3: Option<xkb_mod_index_t>,
|
||||
pub logo: Option<xkb_mod_index_t>,
|
||||
pub mod5: Option<xkb_mod_index_t>,
|
||||
}
|
||||
|
||||
fn mod_index_for_name(keymap: NonNull<xkb_keymap>, name: &[u8]) -> Option<xkb_mod_index_t> {
|
||||
unsafe {
|
||||
let mod_index =
|
||||
(XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char);
|
||||
if mod_index == XKB_MOD_INVALID {
|
||||
None
|
||||
} else {
|
||||
Some(mod_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
465
src/platform_impl/linux/common/xkb/mod.rs
Normal file
465
src/platform_impl/linux/common/xkb/mod.rs
Normal file
@@ -0,0 +1,465 @@
|
||||
use std::ops::Deref;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(wayland_platform)]
|
||||
use std::os::unix::io::OwnedFd;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
|
||||
xkbcommon_handle, XkbCommon, XkbCommonCompose,
|
||||
};
|
||||
#[cfg(x11_platform)]
|
||||
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
|
||||
|
||||
use crate::event::ElementState;
|
||||
use crate::event::KeyEvent;
|
||||
use crate::keyboard::{Key, KeyLocation};
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
|
||||
mod compose;
|
||||
mod keymap;
|
||||
mod state;
|
||||
|
||||
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
|
||||
use keymap::XkbKeymap;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub use keymap::raw_keycode_to_physicalkey;
|
||||
pub use keymap::{physicalkey_to_scancode, scancode_to_keycode};
|
||||
pub use state::XkbState;
|
||||
|
||||
// TODO: Wire this up without using a static `AtomicBool`.
|
||||
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
|
||||
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
|
||||
#[cfg(feature = "x11")]
|
||||
static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reset_dead_keys() {
|
||||
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// libxkbcommon is not available
|
||||
XKBNotFound,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
// NOTE: field order matters.
|
||||
#[cfg(x11_platform)]
|
||||
pub core_keyboard_id: i32,
|
||||
state: Option<XkbState>,
|
||||
keymap: Option<XkbKeymap>,
|
||||
compose_state1: Option<XkbComposeState>,
|
||||
compose_state2: Option<XkbComposeState>,
|
||||
_compose_table: Option<XkbComposeTable>,
|
||||
context: XkbContext,
|
||||
scratch_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
if xkb::xkbcommon_option().is_none() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let context = XkbContext::new()?;
|
||||
let mut compose_table = XkbComposeTable::new(&context);
|
||||
let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state());
|
||||
let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state());
|
||||
|
||||
// Disable compose if anything compose related failed to initialize.
|
||||
if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() {
|
||||
compose_state2 = None;
|
||||
compose_state1 = None;
|
||||
compose_table = None;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
state: None,
|
||||
keymap: None,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
#[cfg(x11_platform)]
|
||||
core_keyboard_id: 0,
|
||||
_compose_table: compose_table,
|
||||
context,
|
||||
scratch_buffer: Vec::with_capacity(8),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result<Self, Error> {
|
||||
let result = unsafe {
|
||||
(XKBXH.xkb_x11_setup_xkb_extension)(
|
||||
xcb,
|
||||
1,
|
||||
2,
|
||||
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if result != 1 {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let mut this = Self::new()?;
|
||||
this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) };
|
||||
this.set_keymap_from_x11(xcb);
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn state_mut(&mut self) -> Option<&mut XkbState> {
|
||||
self.state.as_mut()
|
||||
}
|
||||
|
||||
pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> {
|
||||
self.keymap.as_mut()
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
|
||||
let keymap = XkbKeymap::from_fd(&self.context, fd, size);
|
||||
let state = keymap.as_ref().and_then(XkbState::new_wayland);
|
||||
if keymap.is_none() || state.is_none() {
|
||||
warn!("failed to update xkb keymap");
|
||||
}
|
||||
self.state = state;
|
||||
self.keymap = keymap;
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
|
||||
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
|
||||
let state = keymap
|
||||
.as_ref()
|
||||
.and_then(|keymap| XkbState::new_x11(xcb, keymap));
|
||||
if keymap.is_none() || state.is_none() {
|
||||
warn!("failed to update xkb keymap");
|
||||
}
|
||||
self.state = state;
|
||||
self.keymap = keymap;
|
||||
}
|
||||
|
||||
/// Key builder context with the user provided xkb state.
|
||||
pub fn key_context(&mut self) -> Option<KeyContext<'_>> {
|
||||
let state = self.state.as_mut()?;
|
||||
let keymap = self.keymap.as_mut()?;
|
||||
let compose_state1 = self.compose_state1.as_mut();
|
||||
let compose_state2 = self.compose_state2.as_mut();
|
||||
let scratch_buffer = &mut self.scratch_buffer;
|
||||
Some(KeyContext {
|
||||
state,
|
||||
keymap,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
scratch_buffer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Key builder context with the user provided xkb state.
|
||||
///
|
||||
/// Should be used when the original context must not be altered.
|
||||
#[cfg(x11_platform)]
|
||||
pub fn key_context_with_state<'a>(
|
||||
&'a mut self,
|
||||
state: &'a mut XkbState,
|
||||
) -> Option<KeyContext<'a>> {
|
||||
let keymap = self.keymap.as_mut()?;
|
||||
let compose_state1 = self.compose_state1.as_mut();
|
||||
let compose_state2 = self.compose_state2.as_mut();
|
||||
let scratch_buffer = &mut self.scratch_buffer;
|
||||
Some(KeyContext {
|
||||
state,
|
||||
keymap,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
scratch_buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeyContext<'a> {
|
||||
pub state: &'a mut XkbState,
|
||||
pub keymap: &'a mut XkbKeymap,
|
||||
compose_state1: Option<&'a mut XkbComposeState>,
|
||||
compose_state2: Option<&'a mut XkbComposeState>,
|
||||
scratch_buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> KeyContext<'a> {
|
||||
pub fn process_key_event(
|
||||
&mut self,
|
||||
keycode: u32,
|
||||
state: ElementState,
|
||||
repeat: bool,
|
||||
) -> KeyEvent {
|
||||
let mut event =
|
||||
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
|
||||
let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
|
||||
let (logical_key, location) = event.key();
|
||||
let text = event.text();
|
||||
let (key_without_modifiers, _) = event.key_without_modifiers();
|
||||
let text_with_all_modifiers = event.text_with_all_modifiers();
|
||||
|
||||
let platform_specific = KeyEventExtra {
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
};
|
||||
|
||||
KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
location,
|
||||
state,
|
||||
repeat,
|
||||
platform_specific,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
|
||||
self.scratch_buffer.clear();
|
||||
self.scratch_buffer.reserve(8);
|
||||
loop {
|
||||
let bytes_written = unsafe {
|
||||
(XKBH.xkb_keysym_to_utf8)(
|
||||
keysym,
|
||||
self.scratch_buffer.as_mut_ptr().cast(),
|
||||
self.scratch_buffer.capacity(),
|
||||
)
|
||||
};
|
||||
if bytes_written == 0 {
|
||||
return None;
|
||||
} else if bytes_written == -1 {
|
||||
self.scratch_buffer.reserve(8);
|
||||
} else {
|
||||
unsafe {
|
||||
self.scratch_buffer
|
||||
.set_len(bytes_written.try_into().unwrap())
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the null-terminator
|
||||
self.scratch_buffer.pop();
|
||||
byte_slice_to_smol_str(self.scratch_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyEventResults<'a, 'b> {
|
||||
context: &'a mut KeyContext<'b>,
|
||||
keycode: u32,
|
||||
keysym: u32,
|
||||
compose: ComposeStatus,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventResults<'a, 'b> {
|
||||
fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self {
|
||||
let keysym = context.state.get_one_sym_raw(keycode);
|
||||
|
||||
let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) {
|
||||
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
|
||||
state.reset();
|
||||
context.compose_state2.as_mut().unwrap().reset();
|
||||
}
|
||||
state.feed(keysym)
|
||||
} else {
|
||||
ComposeStatus::None
|
||||
};
|
||||
|
||||
KeyEventResults {
|
||||
context,
|
||||
keycode,
|
||||
keysym,
|
||||
compose,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&mut self) -> (Key, KeyLocation) {
|
||||
let (key, location) = match self.keysym_to_key(self.keysym) {
|
||||
Ok(known) => return known,
|
||||
Err(undefined) => undefined,
|
||||
};
|
||||
|
||||
if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose {
|
||||
let compose_state = self.context.compose_state2.as_mut().unwrap();
|
||||
// When pressing a dead key twice, the non-combining variant of that character will
|
||||
// be produced. Since this function only concerns itself with a single keypress, we
|
||||
// simulate this double press here by feeding the keysym to the compose state
|
||||
// twice.
|
||||
|
||||
compose_state.feed(self.keysym);
|
||||
if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) {
|
||||
// Extracting only a single `char` here *should* be fine, assuming that no
|
||||
// dead key's non-combining variant ever occupies more than one `char`.
|
||||
let text = compose_state.get_string(self.context.scratch_buffer);
|
||||
let key = Key::Dead(text.and_then(|s| s.chars().next()));
|
||||
(key, location)
|
||||
} else {
|
||||
(key, location)
|
||||
}
|
||||
} else {
|
||||
let key = self
|
||||
.composed_text()
|
||||
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key);
|
||||
(key, location)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
|
||||
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
|
||||
let layout = self.context.state.layout(self.keycode);
|
||||
let keysym = self
|
||||
.context
|
||||
.keymap
|
||||
.first_keysym_by_level(layout, self.keycode);
|
||||
|
||||
match self.keysym_to_key(keysym) {
|
||||
Ok((key, location)) => (key, location),
|
||||
Err((key, location)) => {
|
||||
let key = self
|
||||
.context
|
||||
.keysym_to_utf8_raw(keysym)
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key);
|
||||
(key, location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
let location = keymap::keysym_location(keysym);
|
||||
let key = keymap::keysym_to_key(keysym);
|
||||
if matches!(key, Key::Unidentified(_)) {
|
||||
Err((key, location))
|
||||
} else {
|
||||
Ok((key, location))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&mut self) -> Option<SmolStr> {
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
|
||||
}
|
||||
|
||||
// The current behaviour makes it so composing a character overrides attempts to input a
|
||||
// control character with the `Ctrl` key. We can potentially add a configuration option
|
||||
// if someone specifically wants the oppsite behaviour.
|
||||
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
|
||||
match self.composed_text() {
|
||||
Ok(text) => text,
|
||||
Err(_) => self
|
||||
.context
|
||||
.state
|
||||
.get_utf8_raw(self.keycode, self.context.scratch_buffer),
|
||||
}
|
||||
}
|
||||
|
||||
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
|
||||
match self.compose {
|
||||
ComposeStatus::Accepted(status) => match status {
|
||||
xkb_compose_status::XKB_COMPOSE_COMPOSED => {
|
||||
let state = self.context.compose_state1.as_mut().unwrap();
|
||||
Ok(state.get_string(self.context.scratch_buffer))
|
||||
}
|
||||
xkb_compose_status::XKB_COMPOSE_COMPOSING
|
||||
| xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None),
|
||||
xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
|
||||
},
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbContext {
|
||||
context: NonNull<xkb_context>,
|
||||
}
|
||||
|
||||
impl XkbContext {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
|
||||
|
||||
let context = match NonNull::new(context) {
|
||||
Some(context) => context,
|
||||
None => return Err(Error::XKBNotFound),
|
||||
};
|
||||
|
||||
Ok(Self { context })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_context_unref)(self.context.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbContext {
|
||||
type Target = NonNull<xkb_context>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.context
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
|
||||
/// `xkb_state_key_get_utf8`.
|
||||
fn make_string_with<F>(scratch_buffer: &mut Vec<u8>, mut f: F) -> Option<SmolStr>
|
||||
where
|
||||
F: FnMut(*mut c_char, usize) -> i32,
|
||||
{
|
||||
let size = f(ptr::null_mut(), 0);
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
let size = usize::try_from(size).unwrap();
|
||||
scratch_buffer.clear();
|
||||
// The allocated buffer must include space for the null-terminator.
|
||||
scratch_buffer.reserve(size + 1);
|
||||
unsafe {
|
||||
let written = f(
|
||||
scratch_buffer.as_mut_ptr().cast(),
|
||||
scratch_buffer.capacity(),
|
||||
);
|
||||
if usize::try_from(written).unwrap() != size {
|
||||
// This will likely never happen.
|
||||
return None;
|
||||
}
|
||||
scratch_buffer.set_len(size);
|
||||
};
|
||||
|
||||
byte_slice_to_smol_str(scratch_buffer)
|
||||
}
|
||||
|
||||
// NOTE: This is track_caller so we can have more informative line numbers when logging
|
||||
#[track_caller]
|
||||
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
|
||||
std::str::from_utf8(bytes)
|
||||
.map(SmolStr::new)
|
||||
.map_err(|e| {
|
||||
log::warn!(
|
||||
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
|
||||
bytes
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
189
src/platform_impl/linux/common/xkb/state.rs
Normal file
189
src/platform_impl/linux/common/xkb/state.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
//! XKB state.
|
||||
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(x11_platform)]
|
||||
use x11_dl::xlib_xcb::xcb_connection_t;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component,
|
||||
};
|
||||
|
||||
use crate::platform_impl::common::xkb::keymap::XkbKeymap;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform_impl::common::xkb::XKBXH;
|
||||
use crate::platform_impl::common::xkb::{make_string_with, XKBH};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbState {
|
||||
state: NonNull<xkb_state>,
|
||||
modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
impl XkbState {
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> {
|
||||
let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?;
|
||||
Some(Self::new_inner(state))
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> {
|
||||
let state = unsafe {
|
||||
(XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id)
|
||||
};
|
||||
let state = NonNull::new(state)?;
|
||||
Some(Self::new_inner(state))
|
||||
}
|
||||
|
||||
fn new_inner(state: NonNull<xkb_state>) -> Self {
|
||||
let modifiers = ModifiersState::default();
|
||||
let mut this = Self { state, modifiers };
|
||||
this.reload_modifiers();
|
||||
this
|
||||
}
|
||||
|
||||
pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t {
|
||||
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) }
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t {
|
||||
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_DEPRESSED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_LATCHED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_LOCKED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_utf8_raw(
|
||||
&mut self,
|
||||
keycode: xkb_keycode_t,
|
||||
scratch_buffer: &mut Vec<u8>,
|
||||
) -> Option<SmolStr> {
|
||||
make_string_with(scratch_buffer, |ptr, len| unsafe {
|
||||
(XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn modifiers(&self) -> ModifiersState {
|
||||
self.modifiers
|
||||
}
|
||||
|
||||
pub fn update_modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
depressed_group: u32,
|
||||
latched_group: u32,
|
||||
locked_group: u32,
|
||||
) {
|
||||
let mask = unsafe {
|
||||
(XKBH.xkb_state_update_mask)(
|
||||
self.state.as_ptr(),
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
depressed_group,
|
||||
latched_group,
|
||||
locked_group,
|
||||
)
|
||||
};
|
||||
|
||||
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
|
||||
// Effective value of mods have changed, we need to update our state.
|
||||
self.reload_modifiers();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload the modifiers.
|
||||
fn reload_modifiers(&mut self) {
|
||||
self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL);
|
||||
self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT);
|
||||
self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT);
|
||||
self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS);
|
||||
self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO);
|
||||
self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM);
|
||||
}
|
||||
|
||||
/// Check if the modifier is active within xkb.
|
||||
fn mod_name_is_active(&mut self, name: &[u8]) -> bool {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_mod_name_is_active)(
|
||||
self.state.as_ptr(),
|
||||
name.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
|
||||
) > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_unref)(self.state.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||
///
|
||||
/// For some modifiers, this means that the key is currently pressed, others are toggled
|
||||
/// (like caps lock).
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ModifiersState {
|
||||
/// The "control" key
|
||||
pub ctrl: bool,
|
||||
/// The "alt" key
|
||||
pub alt: bool,
|
||||
/// The "shift" key
|
||||
pub shift: bool,
|
||||
/// The "Caps lock" key
|
||||
pub caps_lock: bool,
|
||||
/// The "logo" key
|
||||
///
|
||||
/// Also known as the "windows" key on most keyboards
|
||||
pub logo: bool,
|
||||
/// The "Num lock" key
|
||||
pub num_lock: bool,
|
||||
}
|
||||
|
||||
impl From<ModifiersState> for crate::keyboard::ModifiersState {
|
||||
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
|
||||
let mut to_mods = crate::keyboard::ModifiersState::empty();
|
||||
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
|
||||
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
|
||||
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
|
||||
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
|
||||
to_mods
|
||||
}
|
||||
}
|
||||
@@ -498,7 +498,7 @@ impl<'a> KeyEventResults<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn physical_key(&mut self) -> PhysicalKey {
|
||||
fn physical_key(&self) -> PhysicalKey {
|
||||
keymap::raw_keycode_to_physicalkey(self.keycode)
|
||||
}
|
||||
|
||||
@@ -553,6 +553,7 @@ impl<'a> KeyEventResults<'a> {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.keysym_to_key(keysym)
|
||||
.unwrap_or_else(|(key, location)| {
|
||||
(
|
||||
@@ -565,7 +566,7 @@ impl<'a> KeyEventResults<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn keysym_to_key(&mut self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
let location = super::keymap::keysym_location(keysym);
|
||||
let key = super::keymap::keysym_to_key(keysym);
|
||||
if matches!(key, Key::Unidentified(_)) {
|
||||
|
||||
@@ -521,7 +521,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn reset_dead_keys(&self) {
|
||||
common::xkb_state::reset_dead_keys()
|
||||
common::xkb::reset_dead_keys()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -659,11 +659,11 @@ impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
|
||||
impl PhysicalKeyExtScancode for PhysicalKey {
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
||||
common::keymap::scancode_to_keycode(scancode)
|
||||
common::xkb::scancode_to_keycode(scancode)
|
||||
}
|
||||
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
common::keymap::physicalkey_to_scancode(self)
|
||||
common::xkb::physicalkey_to_scancode(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,8 +757,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
let backend = match (
|
||||
attributes.forced_backend,
|
||||
env::var("WAYLAND_DISPLAY")
|
||||
.map(|var| !var.is_empty())
|
||||
.unwrap_or(false),
|
||||
.ok()
|
||||
.filter(|var| !var.is_empty())
|
||||
.or_else(|| env::var("WAYLAND_SOCKET").ok())
|
||||
.filter(|var| !var.is_empty())
|
||||
.is_some(),
|
||||
env::var("DISPLAY")
|
||||
.map(|var| !var.is_empty())
|
||||
.unwrap_or(false),
|
||||
@@ -772,10 +775,15 @@ impl<T: 'static> EventLoop<T> {
|
||||
#[cfg(x11_platform)]
|
||||
(None, _, true) => Backend::X,
|
||||
// No backend is present.
|
||||
_ => {
|
||||
return Err(EventLoopError::Os(os_error!(OsError::Misc(
|
||||
"neither WAYLAND_DISPLAY nor DISPLAY is set."
|
||||
))));
|
||||
(_, wayland_display, x11_display) => {
|
||||
let msg = if wayland_display && !cfg!(wayland_platform) {
|
||||
"DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland"
|
||||
} else if x11_display && !cfg!(x11_platform) {
|
||||
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11"
|
||||
} else {
|
||||
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
|
||||
};
|
||||
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -784,7 +792,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
#[cfg(wayland_platform)]
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
|
||||
#[cfg(x11_platform)]
|
||||
Backend::X => Ok(EventLoop::new_x11_any_thread().unwrap()),
|
||||
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,10 +802,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
|
||||
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(err) => return Err(err.clone()),
|
||||
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
|
||||
};
|
||||
|
||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
||||
@@ -918,6 +926,10 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit())
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.exit())
|
||||
}
|
||||
@@ -926,10 +938,12 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn set_exit_code(&self, code: i32) {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn exit_code(&self) -> Option<i32> {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
|
||||
}
|
||||
|
||||
@@ -10,13 +10,12 @@ use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
use sctk::reexports::calloop::Error as CalloopError;
|
||||
use sctk::reexports::calloop_wayland_source::WaylandSource;
|
||||
use sctk::reexports::client::globals;
|
||||
use sctk::reexports::client::{Connection, QueueHandle};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::error::{EventLoopError, OsError as RootOsError};
|
||||
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
use crate::event_loop::{
|
||||
@@ -34,7 +33,7 @@ use sink::EventSink;
|
||||
|
||||
use super::state::{WindowCompositorUpdate, WinitState};
|
||||
use super::window::state::FrameCallbackState;
|
||||
use super::{DeviceId, WaylandError, WindowId};
|
||||
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
|
||||
@@ -356,15 +355,13 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
for mut compositor_update in compositor_updates.drain(..) {
|
||||
let window_id = compositor_update.window_id;
|
||||
if let Some(scale_factor) = compositor_update.scale_factor {
|
||||
let physical_size = self.with_state(|state| {
|
||||
if compositor_update.scale_changed {
|
||||
let (physical_size, scale_factor) = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
// Set the new scale factor.
|
||||
window.set_scale_factor(scale_factor);
|
||||
let window_size = compositor_update.size.unwrap_or(window.inner_size());
|
||||
logical_to_physical_rounded(window_size, scale_factor)
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
let scale_factor = window.scale_factor();
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
(size, scale_factor)
|
||||
});
|
||||
|
||||
// Stash the old window size.
|
||||
@@ -386,30 +383,32 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
|
||||
// Resize the window when user altered the size.
|
||||
if old_physical_size != physical_size {
|
||||
self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
window.resize(new_logical_size);
|
||||
});
|
||||
}
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.size = Some(new_logical_size);
|
||||
let new_logical_size: LogicalSize<f64> =
|
||||
physical_size.to_logical(scale_factor);
|
||||
window.request_inner_size(new_logical_size.into());
|
||||
});
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.resized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(size) = compositor_update.size.take() {
|
||||
// NOTE: Rescale changed the physical size which winit operates in, thus we should
|
||||
// resize.
|
||||
if compositor_update.resized || compositor_update.scale_changed {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
|
||||
// TODO could probably bring back size reporting optimization.
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
state
|
||||
@@ -420,7 +419,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
physical_size
|
||||
size
|
||||
});
|
||||
|
||||
callback(
|
||||
@@ -466,45 +465,45 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_ids.extend(state.window_requests.get_mut().keys());
|
||||
});
|
||||
|
||||
for window_id in window_ids.drain(..) {
|
||||
let request_redraw = self.with_state(|state| {
|
||||
for window_id in window_ids.iter() {
|
||||
let event = self.with_state(|state| {
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
if window_requests.get(&window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(&window_id));
|
||||
mem::drop(state.windows.get_mut().remove(&window_id));
|
||||
false
|
||||
} else {
|
||||
let mut window = state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
if window.frame_callback_state() == FrameCallbackState::Requested {
|
||||
false
|
||||
} else {
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let mut redraw_requested = window_requests
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
|
||||
// Redraw the frame while at it.
|
||||
redraw_requested |= window.refresh_frame();
|
||||
|
||||
redraw_requested
|
||||
}
|
||||
if window_requests.get(window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(window_id));
|
||||
mem::drop(state.windows.get_mut().remove(window_id));
|
||||
return Some(WindowEvent::Destroyed);
|
||||
}
|
||||
|
||||
let mut window = state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
if window.frame_callback_state() == FrameCallbackState::Requested {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let mut redraw_requested = window_requests
|
||||
.get(window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
|
||||
// Redraw the frame while at it.
|
||||
redraw_requested |= window.refresh_frame();
|
||||
|
||||
redraw_requested.then_some(WindowEvent::RedrawRequested)
|
||||
});
|
||||
|
||||
if request_redraw {
|
||||
if let Some(event) = event {
|
||||
callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event,
|
||||
},
|
||||
&self.window_target,
|
||||
);
|
||||
@@ -519,6 +518,42 @@ impl<T: 'static> EventLoop<T> {
|
||||
// This is always the last event we dispatch before poll again
|
||||
callback(Event::AboutToWait, &self.window_target);
|
||||
|
||||
// Update the window frames and schedule redraws.
|
||||
let mut wake_up = false;
|
||||
for window_id in window_ids.drain(..) {
|
||||
wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) {
|
||||
Some(window) => {
|
||||
let refresh = window.lock().unwrap().refresh_frame();
|
||||
if refresh {
|
||||
state
|
||||
.window_requests
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
refresh
|
||||
}
|
||||
None => false,
|
||||
});
|
||||
}
|
||||
|
||||
// Wakeup event loop if needed.
|
||||
//
|
||||
// If the user draws from the `AboutToWait` this is likely not required, however
|
||||
// we can't do much about it.
|
||||
if wake_up {
|
||||
match &self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => {
|
||||
window_target.event_loop_awakener.ping();
|
||||
}
|
||||
#[cfg(x11_platform)]
|
||||
PlatformEventLoopWindowTarget::X(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
|
||||
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
|
||||
std::mem::swap(&mut self.window_ids, &mut window_ids);
|
||||
@@ -629,6 +664,34 @@ pub struct EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.control_flow.set(control_flow)
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
self.control_flow.get()
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
self.exit.set(Some(0))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.exit.set(None)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_exit_code(&self, code: i32) {
|
||||
self.exit.set(Some(code))
|
||||
}
|
||||
|
||||
pub(crate) fn exit_code(&self) -> Option<i32> {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
||||
|
||||
@@ -656,10 +719,3 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
// The default routine does floor, but we need round on Wayland.
|
||||
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
|
||||
let width = size.width as f64 * scale_factor;
|
||||
let height = size.height as f64 * scale_factor;
|
||||
(width.round(), height.round()).into()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use sctk::reexports::client::globals::{BindError, GlobalError};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
pub use crate::platform_impl::platform::{OsError, WindowId};
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
@@ -76,3 +77,10 @@ impl DeviceId {
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.id().as_ptr() as u64)
|
||||
}
|
||||
|
||||
/// The default routine does floor, but we need round on Wayland.
|
||||
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
|
||||
let width = size.width as f64 * scale_factor;
|
||||
let height = size.height as f64 * scale_factor;
|
||||
(width.round(), height.round()).into()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use sctk::reexports::client::Proxy;
|
||||
use sctk::output::OutputData;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::platform_impl::platform::VideoMode as PlatformVideoMode;
|
||||
|
||||
use super::event_loop::EventLoopWindowTarget;
|
||||
@@ -24,30 +23,6 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.control_flow.set(control_flow)
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
self.control_flow.get()
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
self.exit.set(Some(0))
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_exit_code(&self, code: i32) {
|
||||
self.exit.set(Some(code))
|
||||
}
|
||||
|
||||
pub(crate) fn exit_code(&self) -> Option<i32> {
|
||||
self.exit.get()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -17,7 +17,7 @@ use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
|
||||
use crate::event::{ElementState, WindowEvent};
|
||||
use crate::keyboard::ModifiersState;
|
||||
|
||||
use crate::platform_impl::common::xkb_state::KbdState;
|
||||
use crate::platform_impl::common::xkb::Context;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::seat::WinitSeatState;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
@@ -43,14 +43,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
WlKeymapFormat::NoKeymap => {
|
||||
warn!("non-xkb compatible keymap")
|
||||
}
|
||||
WlKeymapFormat::XkbV1 => unsafe {
|
||||
seat_state
|
||||
.keyboard_state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.xkb_state
|
||||
.init_with_fd(fd, size as usize);
|
||||
},
|
||||
WlKeymapFormat::XkbV1 => {
|
||||
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
context.set_keymap_from_fd(fd, size as usize);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
WEnum::Unknown(value) => {
|
||||
@@ -61,8 +57,13 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// Mark the window as focused.
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(true),
|
||||
let was_unfocused = match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => {
|
||||
let mut window = window.lock().unwrap();
|
||||
let was_unfocused = !window.has_focus();
|
||||
window.add_seat_focus(data.seat.id());
|
||||
was_unfocused
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
|
||||
@@ -73,13 +74,15 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
// The keyboard focus is considered as general focus.
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
|
||||
*data.window_id.lock().unwrap() = Some(window_id);
|
||||
|
||||
// The keyboard focus is considered as general focus.
|
||||
if was_unfocused {
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
}
|
||||
|
||||
// HACK: this is just for GNOME not fixing their ordering issue of modifiers.
|
||||
if std::mem::take(&mut seat_state.modifiers_pending) {
|
||||
state.events_sink.push_window_event(
|
||||
@@ -101,24 +104,30 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
|
||||
// NOTE: The check whether the window exists is essential as we might get a
|
||||
// nil surface, regardless of what protocol says.
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(false),
|
||||
let focused = match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => {
|
||||
let mut window = window.lock().unwrap();
|
||||
window.remove_seat_focus(&data.seat.id());
|
||||
window.has_focus()
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Notify that no modifiers are being pressed.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// We don't need to update it above, because the next `Enter` will overwrite
|
||||
// anyway.
|
||||
*data.window_id.lock().unwrap() = None;
|
||||
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
if !focused {
|
||||
// Notify that no modifiers are being pressed.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
|
||||
window_id,
|
||||
);
|
||||
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
}
|
||||
}
|
||||
WlKeyboardEvent::Key {
|
||||
key,
|
||||
@@ -142,7 +151,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
RepeatInfo::Disable => return,
|
||||
};
|
||||
|
||||
if !keyboard_state.xkb_state.key_repeats(key) {
|
||||
if !keyboard_state
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,7 +222,11 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
if keyboard_state.repeat_info != RepeatInfo::Disable
|
||||
&& keyboard_state.xkb_state.key_repeats(key)
|
||||
&& keyboard_state
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
&& Some(key) == keyboard_state.current_repeat
|
||||
{
|
||||
keyboard_state.current_repeat = None;
|
||||
@@ -224,9 +242,14 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
group,
|
||||
..
|
||||
} => {
|
||||
let xkb_state = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_state;
|
||||
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
let xkb_state = match xkb_context.state_mut() {
|
||||
Some(state) => state,
|
||||
None => return,
|
||||
};
|
||||
|
||||
xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
seat_state.modifiers = xkb_state.mods_state().into();
|
||||
seat_state.modifiers = xkb_state.modifiers().into();
|
||||
|
||||
// HACK: part of the workaround from `WlKeyboardEvent::Enter`.
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
@@ -272,7 +295,7 @@ pub struct KeyboardState {
|
||||
pub loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// The state of the keyboard.
|
||||
pub xkb_state: KbdState,
|
||||
pub xkb_context: Context,
|
||||
|
||||
/// The information about the repeat rate obtained from the compositor.
|
||||
pub repeat_info: RepeatInfo,
|
||||
@@ -289,7 +312,7 @@ impl KeyboardState {
|
||||
Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
xkb_state: KbdState::new().unwrap(),
|
||||
xkb_context: Context::new().unwrap(),
|
||||
repeat_info: RepeatInfo::default(),
|
||||
repeat_token: None,
|
||||
current_repeat: None,
|
||||
@@ -372,16 +395,13 @@ fn key_input(
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
|
||||
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
let event = keyboard_state
|
||||
.xkb_state
|
||||
.process_key_event(keycode, state, repeat);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
|
||||
let event = key_context.process_key_event(keycode, state, repeat);
|
||||
let event = WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
};
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
|
||||
use ahash::AHashMap;
|
||||
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
@@ -13,6 +14,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
|
||||
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
||||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||
|
||||
use crate::event::WindowEvent;
|
||||
use crate::keyboard::ModifiersState;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
@@ -143,6 +145,10 @@ impl SeatHandler for WinitState {
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
|
||||
|
||||
if let Some(text_input) = seat_state.text_input.take() {
|
||||
text_input.destroy();
|
||||
}
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch => {
|
||||
if let Some(touch) = seat_state.touch.take() {
|
||||
@@ -174,13 +180,10 @@ impl SeatHandler for WinitState {
|
||||
}
|
||||
SeatCapability::Keyboard => {
|
||||
seat_state.keyboard_state = None;
|
||||
self.on_keyboard_destroy(&seat.id());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(text_input) = seat_state.text_input.take() {
|
||||
text_input.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
fn new_seat(
|
||||
@@ -199,6 +202,21 @@ impl SeatHandler for WinitState {
|
||||
seat: WlSeat,
|
||||
) {
|
||||
let _ = self.seats.remove(&seat.id());
|
||||
self.on_keyboard_destroy(&seat.id());
|
||||
}
|
||||
}
|
||||
|
||||
impl WinitState {
|
||||
fn on_keyboard_destroy(&mut self, seat: &ObjectId) {
|
||||
for (window_id, window) in self.windows.get_mut() {
|
||||
let mut window = window.lock().unwrap();
|
||||
let had_focus = window.has_focus();
|
||||
window.remove_seat_focus(seat);
|
||||
if had_focus != window.has_focus() {
|
||||
self.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), *window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,19 +60,34 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
if let zwp_relative_pointer_v1::Event::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
}
|
||||
let (dx_unaccel, dy_unaccel) = match event {
|
||||
zwp_relative_pointer_v1::Event::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} => (dx_unaccel, dy_unaccel),
|
||||
_ => return,
|
||||
};
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::Motion {
|
||||
axis: 0,
|
||||
value: dx_unaccel,
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::Motion {
|
||||
axis: 1,
|
||||
value: dy_unaccel,
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,21 +22,19 @@ use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
use super::seat::{
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::output::MonitorHandle;
|
||||
use crate::platform_impl::wayland::seat::{
|
||||
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
|
||||
WinitPointerDataExt, WinitSeatState,
|
||||
};
|
||||
use super::types::kwin_blur::KWinBlurManager;
|
||||
use super::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use super::types::wp_viewporter::ViewporterState;
|
||||
use super::types::xdg_activation::XdgActivationState;
|
||||
use super::window::{WindowRequests, WindowState};
|
||||
use super::{WaylandError, WindowId};
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
|
||||
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
|
||||
use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
|
||||
use crate::platform_impl::wayland::{WaylandError, WindowId};
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
/// Winit's Wayland state.
|
||||
pub struct WinitState {
|
||||
@@ -50,7 +48,7 @@ pub struct WinitState {
|
||||
pub compositor_state: Arc<CompositorState>,
|
||||
|
||||
/// The state of the subcompositor.
|
||||
pub subcompositor_state: Arc<SubcompositorState>,
|
||||
pub subcompositor_state: Option<Arc<SubcompositorState>>,
|
||||
|
||||
/// The seat state responsible for all sorts of input.
|
||||
pub seat_state: SeatState,
|
||||
@@ -124,12 +122,17 @@ impl WinitState {
|
||||
let registry_state = RegistryState::new(globals);
|
||||
let compositor_state =
|
||||
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let subcompositor_state = SubcompositorState::bind(
|
||||
let subcompositor_state = match SubcompositorState::bind(
|
||||
compositor_state.wl_compositor().clone(),
|
||||
globals,
|
||||
queue_handle,
|
||||
)
|
||||
.map_err(WaylandError::Bind)?;
|
||||
) {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let output_state = OutputState::new(globals, queue_handle);
|
||||
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
|
||||
@@ -151,7 +154,7 @@ impl WinitState {
|
||||
Ok(Self {
|
||||
registry_state,
|
||||
compositor_state: Arc::new(compositor_state),
|
||||
subcompositor_state: Arc::new(subcompositor_state),
|
||||
subcompositor_state: subcompositor_state.map(Arc::new),
|
||||
output_state,
|
||||
seat_state,
|
||||
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
@@ -214,7 +217,7 @@ impl WinitState {
|
||||
|
||||
// Update the scale factor right away.
|
||||
window.lock().unwrap().set_scale_factor(scale_factor);
|
||||
self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
|
||||
self.window_compositor_updates[pos].scale_changed = true;
|
||||
} else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) {
|
||||
// Get the window, where the pointer resides right now.
|
||||
let focused_window = match pointer.pointer().winit_data().focused_window() {
|
||||
@@ -278,23 +281,26 @@ impl WindowHandler for WinitState {
|
||||
};
|
||||
|
||||
// Populate the configure to the window.
|
||||
//
|
||||
// XXX the size on the window will be updated right before dispatching the size to the user.
|
||||
let new_size = self
|
||||
self.window_compositor_updates[pos].resized |= self
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.expect("got configure for dead window.")
|
||||
.lock()
|
||||
.unwrap()
|
||||
.configure(
|
||||
configure,
|
||||
&self.shm,
|
||||
&self.subcompositor_state,
|
||||
&mut self.events_sink,
|
||||
);
|
||||
.configure(configure, &self.shm, &self.subcompositor_state);
|
||||
|
||||
self.window_compositor_updates[pos].size = Some(new_size);
|
||||
// NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the
|
||||
// users, since it can break a lot of things, thus it'll ask users to redraw instead.
|
||||
self.window_requests
|
||||
.get_mut()
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
// Manually mark that we've got an event, since configure may not generate a resize.
|
||||
self.dispatched_events = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,10 +394,10 @@ pub struct WindowCompositorUpdate {
|
||||
pub window_id: WindowId,
|
||||
|
||||
/// New window size.
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
pub resized: bool,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_factor: Option<f64>,
|
||||
pub scale_changed: bool,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
@@ -401,8 +407,8 @@ impl WindowCompositorUpdate {
|
||||
fn new(window_id: WindowId) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
size: None,
|
||||
scale_factor: None,
|
||||
resized: false,
|
||||
scale_changed: false,
|
||||
close_window: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
use sctk::reexports::client::protocol::wl_display::WlDisplay;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
@@ -97,11 +96,9 @@ impl Window {
|
||||
.map(|activation_state| activation_state.global().clone());
|
||||
let display = event_loop_window_target.connection.display();
|
||||
|
||||
// XXX The initial scale factor must be 1, but it might cause sizing issues on HiDPI.
|
||||
let size: LogicalSize<u32> = attributes
|
||||
let size: Size = attributes
|
||||
.inner_size
|
||||
.map(|size| size.to_logical::<u32>(1.))
|
||||
.unwrap_or((800, 600).into());
|
||||
.unwrap_or(LogicalSize::new(800., 600.).into());
|
||||
|
||||
// We prefer server side decorations, however to not have decorations we ask for client
|
||||
// side decorations instead.
|
||||
@@ -141,7 +138,8 @@ impl Window {
|
||||
// Set the window title.
|
||||
window_state.set_title(attributes.title);
|
||||
|
||||
// Set the min and max sizes.
|
||||
// Set the min and max sizes. We must set the hints upon creating a window, so
|
||||
// we use the default `1.` scaling...
|
||||
let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
|
||||
let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
|
||||
window_state.set_min_inner_size(min_size);
|
||||
@@ -281,7 +279,7 @@ impl Window {
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.inner_size().to_physical(scale_factor)
|
||||
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -309,18 +307,15 @@ impl Window {
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.outer_size().to_physical(scale_factor)
|
||||
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
||||
let mut window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.resize(size.to_logical::<u32>(scale_factor));
|
||||
|
||||
let new_size = window_state.request_inner_size(size);
|
||||
self.request_redraw();
|
||||
|
||||
Some(window_state.inner_size().to_physical(scale_factor))
|
||||
Some(new_size)
|
||||
}
|
||||
|
||||
/// Set the minimum inner size for the window.
|
||||
@@ -331,7 +326,9 @@ impl Window {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_min_inner_size(min_size)
|
||||
.set_min_inner_size(min_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Set the maximum inner size for the window.
|
||||
@@ -342,7 +339,9 @@ impl Window {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_max_inner_size(max_size)
|
||||
.set_max_inner_size(max_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -391,7 +390,10 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.window_state.lock().unwrap().set_resizable(resizable);
|
||||
if self.window_state.lock().unwrap().set_resizable(resizable) {
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
//! The state of the window, which is shared with the event-loop.
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use ahash::HashSet;
|
||||
use log::{info, warn};
|
||||
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
@@ -28,11 +29,9 @@ use sctk::shm::Shm;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::event::WindowEvent;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::make_wid;
|
||||
use crate::platform_impl::wayland::logical_to_physical_rounded;
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
|
||||
@@ -55,9 +54,6 @@ pub struct WindowState {
|
||||
/// The connection to Wayland server.
|
||||
pub connection: Connection,
|
||||
|
||||
/// The underlying SCTK window.
|
||||
pub window: ManuallyDrop<Window>,
|
||||
|
||||
/// The window frame, which is created from the configure request.
|
||||
frame: Option<WinitFrame>,
|
||||
|
||||
@@ -91,8 +87,10 @@ pub struct WindowState {
|
||||
/// Whether the frame is resizable.
|
||||
resizable: bool,
|
||||
|
||||
/// Whether the window has focus.
|
||||
has_focus: bool,
|
||||
// NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new
|
||||
// is created, since add/removed stuff could be delivered a bit out of order.
|
||||
/// Seats that has keyboard focus on that window.
|
||||
seat_focus: HashSet<ObjectId>,
|
||||
|
||||
/// The scale factor of the window.
|
||||
scale_factor: f64,
|
||||
@@ -133,6 +131,10 @@ pub struct WindowState {
|
||||
/// sends `None` for the new size in the configure.
|
||||
stateless_size: LogicalSize<u32>,
|
||||
|
||||
/// Initial window size provided by the user. Removed on the first
|
||||
/// configure.
|
||||
initial_size: Option<Size>,
|
||||
|
||||
/// The state of the frame callback.
|
||||
frame_callback_state: FrameCallbackState,
|
||||
|
||||
@@ -145,6 +147,9 @@ pub struct WindowState {
|
||||
///
|
||||
/// The value is the serial of the event triggered moved.
|
||||
has_pending_move: Option<u32>,
|
||||
|
||||
/// The underlying SCTK window.
|
||||
pub window: Window,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
@@ -153,7 +158,7 @@ impl WindowState {
|
||||
connection: Connection,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
winit_state: &WinitState,
|
||||
size: LogicalSize<u32>,
|
||||
initial_size: Size,
|
||||
window: Window,
|
||||
theme: Option<Theme>,
|
||||
) -> Self {
|
||||
@@ -181,7 +186,7 @@ impl WindowState {
|
||||
fractional_scale,
|
||||
frame: None,
|
||||
frame_callback_state: FrameCallbackState::None,
|
||||
has_focus: false,
|
||||
seat_focus: Default::default(),
|
||||
has_pending_move: None,
|
||||
ime_allowed: false,
|
||||
ime_purpose: ImePurpose::Normal,
|
||||
@@ -194,14 +199,15 @@ impl WindowState {
|
||||
resizable: true,
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
size,
|
||||
stateless_size: size,
|
||||
size: initial_size.to_logical(1.),
|
||||
stateless_size: initial_size.to_logical(1.),
|
||||
initial_size: Some(initial_size),
|
||||
text_inputs: Vec::new(),
|
||||
theme,
|
||||
title: String::default(),
|
||||
transparent: false,
|
||||
viewport,
|
||||
window: ManuallyDrop::new(window),
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,16 +256,26 @@ impl WindowState {
|
||||
&mut self,
|
||||
configure: WindowConfigure,
|
||||
shm: &Shm,
|
||||
subcompositor: &Arc<SubcompositorState>,
|
||||
event_sink: &mut EventSink,
|
||||
) -> LogicalSize<u32> {
|
||||
if configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
{
|
||||
subcompositor: &Option<Arc<SubcompositorState>>,
|
||||
) -> bool {
|
||||
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
|
||||
// should be delivered before the first configure, thus apply it to
|
||||
// properly scale the physical sizes provided by the users.
|
||||
if let Some(initial_size) = self.initial_size.take() {
|
||||
self.size = initial_size.to_logical(self.scale_factor());
|
||||
self.stateless_size = self.size;
|
||||
}
|
||||
|
||||
if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
|
||||
configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
}) {
|
||||
match WinitFrame::new(
|
||||
&*self.window,
|
||||
&self.window,
|
||||
shm,
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
self.compositor.clone(),
|
||||
subcompositor.clone(),
|
||||
self.queue_handle.clone(),
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
@@ -284,50 +300,90 @@ impl WindowState {
|
||||
|
||||
let stateless = Self::is_stateless(&configure);
|
||||
|
||||
// Emit `Occluded` event on suspension change.
|
||||
let occluded = configure.state.contains(XdgWindowState::SUSPENDED);
|
||||
if self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|c| c.state.contains(XdgWindowState::SUSPENDED))
|
||||
.unwrap_or(false)
|
||||
!= occluded
|
||||
{
|
||||
let window_id = make_wid(self.window.wl_surface());
|
||||
event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id);
|
||||
}
|
||||
|
||||
let new_size = if let Some(frame) = self.frame.as_mut() {
|
||||
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
|
||||
// Configure the window states.
|
||||
frame.update_state(configure.state);
|
||||
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => {
|
||||
let (width, height) = frame.subtract_borders(width, height);
|
||||
(
|
||||
width.map(|w| w.get()).unwrap_or(1),
|
||||
height.map(|h| h.get()).unwrap_or(1),
|
||||
)
|
||||
.into()
|
||||
let width = width.map(|w| w.get()).unwrap_or(1);
|
||||
let height = height.map(|h| h.get()).unwrap_or(1);
|
||||
((width, height).into(), false)
|
||||
}
|
||||
(_, _) if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
(_, _) if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
}
|
||||
} else {
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => (width.get(), height.get()).into(),
|
||||
_ if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
(Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
|
||||
_ if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
}
|
||||
};
|
||||
|
||||
// XXX Set the configure before doing a resize.
|
||||
// Apply configure bounds only when compositor let the user decide what size to pick.
|
||||
if constrain {
|
||||
let bounds = self.inner_size_bounds(&configure);
|
||||
new_size.width = bounds
|
||||
.0
|
||||
.map(|bound_w| new_size.width.min(bound_w.get()))
|
||||
.unwrap_or(new_size.width);
|
||||
new_size.height = bounds
|
||||
.1
|
||||
.map(|bound_h| new_size.height.min(bound_h.get()))
|
||||
.unwrap_or(new_size.height);
|
||||
}
|
||||
|
||||
let new_state = configure.state;
|
||||
let old_state = self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|configure| configure.state);
|
||||
|
||||
let state_change_requires_resize = old_state
|
||||
.map(|old_state| {
|
||||
!old_state
|
||||
.symmetric_difference(new_state)
|
||||
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
|
||||
.is_empty()
|
||||
})
|
||||
// NOTE: `None` is present for the initial configure, thus we must always resize.
|
||||
.unwrap_or(true);
|
||||
|
||||
// NOTE: Set the configure before doing a resize, since we query it during it.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
// XXX Update the new size right away.
|
||||
self.resize(new_size);
|
||||
if state_change_requires_resize || new_size != self.inner_size() {
|
||||
self.resize(new_size);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
new_size
|
||||
/// Compute the bounds for the inner size of the surface.
|
||||
fn inner_size_bounds(
|
||||
&self,
|
||||
configure: &WindowConfigure,
|
||||
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
|
||||
let configure_bounds = match configure.suggested_bounds {
|
||||
Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
if let Some(frame) = self.frame.as_ref() {
|
||||
let (width, height) = frame.subtract_borders(
|
||||
configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
|
||||
configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
|
||||
);
|
||||
(
|
||||
configure_bounds.0.and(width),
|
||||
configure_bounds.1.and(height),
|
||||
)
|
||||
} else {
|
||||
configure_bounds
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -442,10 +498,12 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Set the resizable state on the window.
|
||||
///
|
||||
/// Returns `true` when the state was applied.
|
||||
#[inline]
|
||||
pub fn set_resizable(&mut self, resizable: bool) {
|
||||
pub fn set_resizable(&mut self, resizable: bool) -> bool {
|
||||
if self.resizable == resizable {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
self.resizable = resizable;
|
||||
@@ -461,12 +519,14 @@ impl WindowState {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_resizable(resizable);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether the window is focused.
|
||||
/// Whether the window is focused by any seat.
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.has_focus
|
||||
!self.seat_focus.is_empty()
|
||||
}
|
||||
|
||||
/// Whether the IME is allowed.
|
||||
@@ -537,7 +597,7 @@ impl WindowState {
|
||||
/// Refresh the decorations frame if it's present returning whether the client should redraw.
|
||||
pub fn refresh_frame(&mut self) -> bool {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
if frame.is_dirty() {
|
||||
if !frame.is_hidden() && frame.is_dirty() {
|
||||
return frame.draw();
|
||||
}
|
||||
}
|
||||
@@ -568,8 +628,22 @@ impl WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to resize the window when the user can do so.
|
||||
pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
|
||||
if self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(Self::is_stateless)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
self.resize(inner_size.to_logical(self.scale_factor()))
|
||||
}
|
||||
|
||||
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
|
||||
}
|
||||
|
||||
/// Resize the window to the new inner size.
|
||||
pub fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
self.size = inner_size;
|
||||
|
||||
// Update the stateless size.
|
||||
@@ -684,9 +758,14 @@ impl WindowState {
|
||||
|
||||
/// Set the cursor grabbing state on the top-level.
|
||||
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
// Replace the user grabbing mode.
|
||||
if self.cursor_grab_mode.user_grab_mode == mode {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.set_cursor_grab_inner(mode)?;
|
||||
// Update user grab on success.
|
||||
self.cursor_grab_mode.user_grab_mode = mode;
|
||||
self.set_cursor_grab_inner(mode)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reload the hints for minimum and maximum sizes.
|
||||
@@ -814,12 +893,16 @@ impl WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark that the window has focus.
|
||||
///
|
||||
/// Should be used from routine that sends focused event.
|
||||
/// Add seat focus for the window.
|
||||
#[inline]
|
||||
pub fn set_has_focus(&mut self, has_focus: bool) {
|
||||
self.has_focus = has_focus;
|
||||
pub fn add_seat_focus(&mut self, seat: ObjectId) {
|
||||
self.seat_focus.insert(seat);
|
||||
}
|
||||
|
||||
/// Remove seat focus from the window.
|
||||
#[inline]
|
||||
pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
|
||||
self.seat_focus.remove(seat);
|
||||
}
|
||||
|
||||
/// Returns `true` if the requested state was applied.
|
||||
@@ -843,7 +926,7 @@ impl WindowState {
|
||||
|
||||
/// Set the IME position.
|
||||
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// FIXME: This won't fly unless user will have a way to request IME window per seat, since
|
||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
||||
// which seat we're setting IME position.
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
@@ -874,7 +957,7 @@ impl WindowState {
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f64) {
|
||||
self.scale_factor = scale_factor;
|
||||
|
||||
// XXX when fractional scaling is not used update the buffer scale.
|
||||
// NOTE: When fractional scaling is not used update the buffer scale.
|
||||
if self.fractional_scale.is_none() {
|
||||
let _ = self.window.set_buffer_scale(self.scale_factor as _);
|
||||
}
|
||||
@@ -959,13 +1042,6 @@ impl WindowState {
|
||||
|
||||
impl Drop for WindowState {
|
||||
fn drop(&mut self) {
|
||||
let surface = self.window.wl_surface().clone();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.window);
|
||||
}
|
||||
|
||||
// Cleanup objects.
|
||||
|
||||
if let Some(blur) = self.blur.take() {
|
||||
blur.release();
|
||||
}
|
||||
@@ -978,7 +1054,8 @@ impl Drop for WindowState {
|
||||
viewport.destroy();
|
||||
}
|
||||
|
||||
surface.destroy();
|
||||
// NOTE: the wl_surface used by the window is being cleaned up when
|
||||
// dropping SCTK `Window`.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,7 +1105,7 @@ impl From<ResizeDirection> for XdgResizeEdge {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX rust doesn't allow from `Option`.
|
||||
// NOTE: Rust doesn't allow `From<Option<Theme>>`.
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
|
||||
match theme {
|
||||
|
||||
@@ -6,7 +6,7 @@ macro_rules! atom_manager {
|
||||
($($name:ident $(:$lit:literal)?),*) => {
|
||||
x11rb::atom_manager! {
|
||||
/// The atoms used by `winit`
|
||||
pub(crate) Atoms: AtomsCookie {
|
||||
pub Atoms: AtomsCookie {
|
||||
$($name $(:$lit)?,)*
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ macro_rules! atom_manager {
|
||||
/// Indices into the `Atoms` struct.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) enum AtomName {
|
||||
pub enum AtomName {
|
||||
$($name,)*
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ atom_manager! {
|
||||
_NET_FRAME_EXTENTS,
|
||||
_NET_SUPPORTED,
|
||||
_NET_SUPPORTING_WM_CHECK,
|
||||
_XEMBED
|
||||
_XEMBED,
|
||||
_XSETTINGS_SETTINGS
|
||||
}
|
||||
|
||||
impl Index<AtomName> for Atoms {
|
||||
|
||||
@@ -41,7 +41,7 @@ impl From<io::Error> for DndDataParseError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Dnd {
|
||||
pub struct Dnd {
|
||||
xconn: Arc<XConnection>,
|
||||
// Populated by XdndEnter event handler
|
||||
pub version: Option<c_long>,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1 @@
|
||||
use x11_dl::xmd::CARD32;
|
||||
pub use x11_dl::{
|
||||
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
|
||||
};
|
||||
|
||||
// Isn't defined by x11_dl
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const IconicState: CARD32 = 3;
|
||||
pub use x11_dl::{error::OpenError, xcursor::*, xinput2::*, xlib::*, xlib_xcb::*};
|
||||
|
||||
@@ -79,9 +79,9 @@ pub(crate) unsafe fn set_destroy_callback(
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ReplaceImError {
|
||||
// Boxed to prevent large error type
|
||||
MethodOpenFailed(Box<PotentialInputMethods>),
|
||||
ContextCreationFailed(ImeContextCreationError),
|
||||
SetDestroyCallbackFailed(XError),
|
||||
MethodOpenFailed(#[allow(dead_code)] Box<PotentialInputMethods>),
|
||||
ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError),
|
||||
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
|
||||
}
|
||||
|
||||
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
|
||||
|
||||
@@ -159,9 +159,9 @@ impl InputMethodResult {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum GetXimServersError {
|
||||
XError(XError),
|
||||
GetPropertyError(util::GetPropertyError),
|
||||
InvalidUtf8(IntoStringError),
|
||||
XError(#[allow(dead_code)] XError),
|
||||
GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
|
||||
InvalidUtf8(#[allow(dead_code)] IntoStringError),
|
||||
}
|
||||
|
||||
impl From<util::GetPropertyError> for GetXimServersError {
|
||||
|
||||
@@ -48,7 +48,7 @@ pub enum ImeRequest {
|
||||
pub(crate) enum ImeCreationError {
|
||||
// Boxed to prevent large error type
|
||||
OpenFailure(Box<PotentialInputMethods>),
|
||||
SetDestroyCallbackFailed(XError),
|
||||
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
|
||||
}
|
||||
|
||||
pub(crate) struct Ime {
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
#![cfg(x11_platform)]
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::Deref;
|
||||
use std::os::raw::*;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{ptr, slice, str};
|
||||
|
||||
pub use self::xdisplay::{XError, XNotSupported};
|
||||
|
||||
use calloop::generic::Generic;
|
||||
use calloop::EventLoop as Loop;
|
||||
use calloop::{ping::Ping, Readiness};
|
||||
use libc::{setlocale, LC_CTYPE};
|
||||
use log::warn;
|
||||
|
||||
use x11rb::connection::RequestConnection;
|
||||
use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError};
|
||||
use x11rb::protocol::xinput::{self, ConnectionExt as _};
|
||||
use x11rb::protocol::xkb;
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _};
|
||||
use x11rb::x11_utils::X11Error as LogicalError;
|
||||
use x11rb::xcb_ffi::ReplyOrIdError;
|
||||
|
||||
use super::{ControlFlow, OsError};
|
||||
use crate::{
|
||||
error::{EventLoopError, OsError as RootOsError},
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
platform::pump_events::PumpStatus,
|
||||
platform_impl::common::xkb::Context,
|
||||
platform_impl::{
|
||||
platform::{min_timeout, WindowId},
|
||||
PlatformSpecificWindowBuilderAttributes,
|
||||
},
|
||||
window::WindowAttributes,
|
||||
};
|
||||
|
||||
mod activation;
|
||||
mod atoms;
|
||||
mod dnd;
|
||||
@@ -10,77 +54,23 @@ mod monitor;
|
||||
pub mod util;
|
||||
mod window;
|
||||
mod xdisplay;
|
||||
|
||||
pub(crate) use self::{
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::UnownedWindow,
|
||||
xdisplay::XConnection,
|
||||
};
|
||||
|
||||
pub use self::xdisplay::{XError, XNotSupported};
|
||||
|
||||
use calloop::generic::Generic;
|
||||
use calloop::EventLoop as Loop;
|
||||
use calloop::{ping::Ping, Readiness};
|
||||
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::CStr,
|
||||
fmt,
|
||||
mem::MaybeUninit,
|
||||
ops::Deref,
|
||||
os::{
|
||||
raw::*,
|
||||
unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
|
||||
},
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice, str,
|
||||
sync::mpsc::{Receiver, Sender, TryRecvError},
|
||||
sync::{mpsc, Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use libc::{self, setlocale, LC_CTYPE};
|
||||
mod xsettings;
|
||||
|
||||
use atoms::*;
|
||||
|
||||
use x11rb::x11_utils::X11Error as LogicalError;
|
||||
use x11rb::{
|
||||
connection::RequestConnection,
|
||||
protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
xproto::{self, ConnectionExt as _},
|
||||
},
|
||||
};
|
||||
use x11rb::{
|
||||
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
|
||||
xcb_ffi::ReplyOrIdError,
|
||||
};
|
||||
|
||||
use self::{
|
||||
dnd::{Dnd, DndState},
|
||||
event_processor::EventProcessor,
|
||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
|
||||
};
|
||||
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
|
||||
use crate::{
|
||||
error::{EventLoopError, OsError as RootOsError},
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
platform::pump_events::PumpStatus,
|
||||
platform_impl::{
|
||||
platform::{min_timeout, WindowId},
|
||||
PlatformSpecificWindowBuilderAttributes,
|
||||
},
|
||||
window::WindowAttributes,
|
||||
};
|
||||
use dnd::{Dnd, DndState};
|
||||
use event_processor::{EventProcessor, MAX_MOD_REPLAY_LEN};
|
||||
use ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender};
|
||||
pub(crate) use monitor::{MonitorHandle, VideoMode};
|
||||
use window::UnownedWindow;
|
||||
pub(crate) use xdisplay::XConnection;
|
||||
|
||||
// Xinput constants not defined in x11rb
|
||||
const ALL_DEVICES: u16 = 0;
|
||||
const ALL_MASTER_DEVICES: u16 = 1;
|
||||
const ICONIC_STATE: u32 = 3;
|
||||
|
||||
/// The underlying x11rb connection that we are using.
|
||||
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
|
||||
|
||||
type X11Source = Generic<BorrowedFd<'static>>;
|
||||
|
||||
@@ -150,7 +140,7 @@ pub struct EventLoopWindowTarget<T> {
|
||||
control_flow: Cell<ControlFlow>,
|
||||
exit: Cell<Option<i32>>,
|
||||
root: xproto::Window,
|
||||
ime: RefCell<Ime>,
|
||||
ime: Option<RefCell<Ime>>,
|
||||
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
activation_sender: WakeSender<ActivationToken>,
|
||||
@@ -167,7 +157,6 @@ pub struct EventLoop<T: 'static> {
|
||||
user_receiver: PeekableReceiver<T>,
|
||||
activation_receiver: PeekableReceiver<ActivationToken>,
|
||||
user_sender: Sender<T>,
|
||||
target: Rc<RootELW<T>>,
|
||||
|
||||
/// The current state of the event loop.
|
||||
state: EventLoopState,
|
||||
@@ -228,13 +217,15 @@ impl<T: 'static> EventLoop<T> {
|
||||
setlocale(LC_CTYPE, default_locale);
|
||||
}
|
||||
}
|
||||
let ime = RefCell::new({
|
||||
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:#?}");
|
||||
}
|
||||
result.expect("Failed to set input method destruction callback")
|
||||
});
|
||||
|
||||
let ime = Ime::new(Arc::clone(&xconn), ime_event_sender);
|
||||
if let Err(ImeCreationError::OpenFailure(state)) = ime.as_ref() {
|
||||
warn!("Failed to open input method: {state:#?}");
|
||||
} else if let Err(err) = ime.as_ref() {
|
||||
warn!("Failed to set input method destruction callback: {err:?}");
|
||||
}
|
||||
|
||||
let ime = ime.ok().map(RefCell::new);
|
||||
|
||||
let randr_event_offset = xconn
|
||||
.select_xrandr_input(root)
|
||||
@@ -298,8 +289,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Create a channel for sending user events.
|
||||
let (user_sender, user_channel) = mpsc::channel();
|
||||
|
||||
let kb_state =
|
||||
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
||||
let xkb_context =
|
||||
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
||||
|
||||
let mut xmodmap = util::ModifierKeymap::new();
|
||||
xmodmap.reload_from_x_connection(&xconn);
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
ime,
|
||||
@@ -326,32 +320,36 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Set initial device event filter.
|
||||
window_target.update_listen_device_events(true);
|
||||
|
||||
let target = Rc::new(RootELW {
|
||||
let root_window_target = RootELW {
|
||||
p: super::EventLoopWindowTarget::X(window_target),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
});
|
||||
_marker: PhantomData,
|
||||
};
|
||||
|
||||
let event_processor = EventProcessor {
|
||||
target: target.clone(),
|
||||
target: root_window_target,
|
||||
dnd,
|
||||
devices: Default::default(),
|
||||
randr_event_offset,
|
||||
ime_receiver,
|
||||
ime_event_receiver,
|
||||
xi2ext,
|
||||
xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN),
|
||||
xmodmap,
|
||||
xkbext,
|
||||
kb_state,
|
||||
xkb_context,
|
||||
num_touch: 0,
|
||||
held_key_press: None,
|
||||
first_touch: None,
|
||||
active_window: None,
|
||||
modifiers: Default::default(),
|
||||
is_composing: false,
|
||||
};
|
||||
|
||||
// Register for device hotplug events
|
||||
// (The request buffer is flushed during `init_device`)
|
||||
get_xtarget(&target)
|
||||
.xconn
|
||||
let xconn = &EventProcessor::window_target(&event_processor.target).xconn;
|
||||
|
||||
xconn
|
||||
.select_xinput_events(
|
||||
root,
|
||||
ALL_DEVICES,
|
||||
@@ -359,11 +357,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
)
|
||||
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
|
||||
|
||||
get_xtarget(&target)
|
||||
.xconn
|
||||
xconn
|
||||
.select_xkb_events(
|
||||
0x100, // Use the "core keyboard device"
|
||||
xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY,
|
||||
xkb::EventType::NEW_KEYBOARD_NOTIFY
|
||||
| xkb::EventType::MAP_NOTIFY
|
||||
| xkb::EventType::STATE_NOTIFY,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -378,7 +377,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
|
||||
user_receiver: PeekableReceiver::from_recv(user_channel),
|
||||
user_sender,
|
||||
target,
|
||||
state: EventLoopState {
|
||||
x11_readiness: Readiness::EMPTY,
|
||||
},
|
||||
@@ -395,7 +393,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
pub(crate) fn window_target(&self) -> &RootELW<T> {
|
||||
&self.target
|
||||
&self.event_processor.target
|
||||
}
|
||||
|
||||
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
@@ -424,7 +422,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// `run_on_demand` calls but if they have only just dropped their
|
||||
// windows we need to make sure those last requests are sent to the
|
||||
// X Server.
|
||||
let wt = get_xtarget(&self.target);
|
||||
let wt = EventProcessor::window_target(&self.event_processor.target);
|
||||
wt.x_connection().sync_with_server().map_err(|x_err| {
|
||||
EventLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err)))))
|
||||
})?;
|
||||
@@ -547,12 +545,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<T>, &RootELW<T>),
|
||||
{
|
||||
callback(crate::event::Event::NewEvents(cause), &self.target);
|
||||
callback(Event::NewEvents(cause), &self.event_processor.target);
|
||||
|
||||
// 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 {
|
||||
callback(crate::event::Event::Resumed, &self.target);
|
||||
callback(Event::Resumed, &self.event_processor.target);
|
||||
}
|
||||
|
||||
// Process all pending events
|
||||
@@ -567,16 +565,16 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
|
||||
match token {
|
||||
Some(Ok(token)) => callback(
|
||||
crate::event::Event::WindowEvent {
|
||||
Some(Ok(token)) => {
|
||||
let event = Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: crate::event::WindowEvent::ActivationTokenDone {
|
||||
event: WindowEvent::ActivationTokenDone {
|
||||
serial,
|
||||
token: crate::window::ActivationToken::_new(token),
|
||||
},
|
||||
},
|
||||
&self.target,
|
||||
),
|
||||
};
|
||||
callback(event, &self.event_processor.target)
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
log::error!("Failed to get activation token: {}", e);
|
||||
}
|
||||
@@ -587,7 +585,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Empty the user event buffer
|
||||
{
|
||||
while let Ok(event) = self.user_receiver.try_recv() {
|
||||
callback(crate::event::Event::UserEvent(event), &self.target);
|
||||
callback(Event::UserEvent(event), &self.event_processor.target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,14 +604,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_id,
|
||||
event: WindowEvent::RedrawRequested,
|
||||
},
|
||||
&self.target,
|
||||
&self.event_processor.target,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This is always the last event we dispatch before poll again
|
||||
{
|
||||
callback(crate::event::Event::AboutToWait, &self.target);
|
||||
callback(Event::AboutToWait, &self.event_processor.target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,40 +619,44 @@ impl<T: 'static> EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<T>, &RootELW<T>),
|
||||
{
|
||||
let target = &self.target;
|
||||
let mut xev = MaybeUninit::uninit();
|
||||
let wt = get_xtarget(&self.target);
|
||||
|
||||
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
||||
let mut xev = unsafe { xev.assume_init() };
|
||||
self.event_processor.process_event(&mut xev, |event| {
|
||||
if let Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(wid),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
} = event
|
||||
{
|
||||
wt.redraw_sender.send(wid).unwrap();
|
||||
} else {
|
||||
callback(event, target);
|
||||
}
|
||||
});
|
||||
self.event_processor
|
||||
.process_event(&mut xev, |window_target, event| {
|
||||
if let Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(wid),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
} = event
|
||||
{
|
||||
let window_target = EventProcessor::window_target(window_target);
|
||||
window_target.redraw_sender.send(wid).unwrap();
|
||||
} else {
|
||||
callback(event, window_target);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
self.target.p.control_flow()
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.control_flow()
|
||||
}
|
||||
|
||||
fn exiting(&self) -> bool {
|
||||
self.target.p.exiting()
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.exiting()
|
||||
}
|
||||
|
||||
fn set_exit_code(&self, code: i32) {
|
||||
self.target.p.set_exit_code(code)
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.set_exit_code(code);
|
||||
}
|
||||
|
||||
fn exit_code(&self) -> Option<i32> {
|
||||
self.target.p.exit_code()
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.exit_code()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,14 +672,6 @@ impl<T> AsRawFd for EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
|
||||
match target.p {
|
||||
super::EventLoopWindowTarget::X(ref target) => target,
|
||||
#[cfg(wayland_platform)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
/// Returns the `XConnection` of this events loop.
|
||||
#[inline]
|
||||
@@ -751,6 +745,10 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
self.exit.set(Some(0))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.exit.set(None)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get().is_some()
|
||||
}
|
||||
@@ -890,6 +888,9 @@ pub enum X11Error {
|
||||
|
||||
/// Could not find a matching X11 visual for this visualid
|
||||
NoSuchVisual(xproto::Visualid),
|
||||
|
||||
/// Unable to parse xsettings.
|
||||
XsettingsParse(xsettings::ParserError),
|
||||
}
|
||||
|
||||
impl fmt::Display for X11Error {
|
||||
@@ -914,6 +915,9 @@ impl fmt::Display for X11Error {
|
||||
visualid
|
||||
)
|
||||
}
|
||||
X11Error::XsettingsParse(err) => {
|
||||
write!(f, "Failed to parse xsettings: {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -982,8 +986,11 @@ impl From<ReplyOrIdError> for X11Error {
|
||||
}
|
||||
}
|
||||
|
||||
/// The underlying x11rb connection that we are using.
|
||||
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
|
||||
impl From<xsettings::ParserError> for X11Error {
|
||||
fn from(value: xsettings::ParserError) -> Self {
|
||||
Self::XsettingsParse(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for a void cookie.
|
||||
type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>;
|
||||
@@ -1000,34 +1007,6 @@ impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to
|
||||
/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed
|
||||
struct GenericEventCookie<'a> {
|
||||
xconn: &'a XConnection,
|
||||
cookie: ffi::XGenericEventCookie,
|
||||
}
|
||||
|
||||
impl<'a> GenericEventCookie<'a> {
|
||||
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 {
|
||||
Some(GenericEventCookie { xconn, cookie })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for GenericEventCookie<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
|
||||
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
|
||||
}
|
||||
@@ -1036,7 +1015,7 @@ fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Device {
|
||||
pub struct Device {
|
||||
_name: String,
|
||||
scroll_axes: Vec<(i32, ScrollAxis)>,
|
||||
// For master devices, this is the paired device (pointer <-> keyboard).
|
||||
@@ -1128,3 +1107,9 @@ impl Device {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the raw X11 representation for a 32-bit floating point to a double.
|
||||
#[inline]
|
||||
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
|
||||
(fp as f64) / ((1 << 16) as f64)
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ impl XConnection {
|
||||
return Ok(MonitorHandle::dummy());
|
||||
}
|
||||
|
||||
let default = monitors.get(0).unwrap();
|
||||
let default = monitors.first().unwrap();
|
||||
|
||||
let window_rect = match window_rect {
|
||||
Some(rect) => rect,
|
||||
@@ -234,7 +234,8 @@ impl XConnection {
|
||||
|
||||
fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, X11Error> {
|
||||
let root = self.default_root();
|
||||
let resources = ScreenResources::from_connection(self.xcb_connection(), root)?;
|
||||
let resources =
|
||||
ScreenResources::from_connection(self.xcb_connection(), root, self.randr_version())?;
|
||||
|
||||
// Pipeline all of the get-crtc requests.
|
||||
let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
|
||||
@@ -284,22 +285,16 @@ impl XConnection {
|
||||
|
||||
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
|
||||
let mut monitors_lock = self.monitor_handles.lock().unwrap();
|
||||
(*monitors_lock)
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.map(Ok)
|
||||
.or_else(|| {
|
||||
self.query_monitor_list()
|
||||
.map(|mon_list| {
|
||||
let monitors = Some(mon_list);
|
||||
if !DISABLE_MONITOR_LIST_CACHING {
|
||||
(*monitors_lock) = monitors.clone();
|
||||
}
|
||||
monitors
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
.unwrap()
|
||||
match *monitors_lock {
|
||||
Some(ref monitors) => Ok(monitors.clone()),
|
||||
None => {
|
||||
let monitors = self.query_monitor_list()?;
|
||||
if !DISABLE_MONITOR_LIST_CACHING {
|
||||
*monitors_lock = Some(monitors.clone());
|
||||
}
|
||||
Ok(monitors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -329,7 +324,7 @@ impl XConnection {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ScreenResources {
|
||||
pub struct ScreenResources {
|
||||
/// List of attached modes.
|
||||
modes: Vec<randr::ModeInfo>,
|
||||
|
||||
@@ -349,10 +344,9 @@ impl ScreenResources {
|
||||
pub(crate) fn from_connection(
|
||||
conn: &impl x11rb::connection::Connection,
|
||||
root: &x11rb::protocol::xproto::Screen,
|
||||
(major_version, minor_version): (u32, u32),
|
||||
) -> Result<Self, X11Error> {
|
||||
let version = conn.randr_query_version(0, 0)?.reply()?;
|
||||
|
||||
if (version.major_version == 1 && version.minor_version >= 3) || version.major_version > 1 {
|
||||
if (major_version == 1 && minor_version >= 3) || major_version > 1 {
|
||||
let reply = conn
|
||||
.randr_get_screen_resources_current(root.root)?
|
||||
.reply()?;
|
||||
|
||||
1
src/platform_impl/linux/x11/tests/xsettings.dat
Normal file
1
src/platform_impl/linux/x11/tests/xsettings.dat
Normal file
File diff suppressed because one or more lines are too long
55
src/platform_impl/linux/x11/util/cookie.rs
Normal file
55
src/platform_impl/linux/x11/util/cookie.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::ffi::c_int;
|
||||
use std::sync::Arc;
|
||||
|
||||
use x11_dl::xlib::{self, XEvent, XGenericEventCookie};
|
||||
|
||||
use crate::platform_impl::x11::XConnection;
|
||||
|
||||
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure.
|
||||
/// This is a wrapper to extract the cookie from a GenericEvent XEvent and release the cookie data
|
||||
/// once it has been processed
|
||||
pub struct GenericEventCookie {
|
||||
cookie: XGenericEventCookie,
|
||||
xconn: Arc<XConnection>,
|
||||
}
|
||||
|
||||
impl GenericEventCookie {
|
||||
pub fn from_event(xconn: Arc<XConnection>, event: XEvent) -> Option<GenericEventCookie> {
|
||||
unsafe {
|
||||
let mut cookie: XGenericEventCookie = From::from(event);
|
||||
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == xlib::True {
|
||||
Some(GenericEventCookie { cookie, xconn })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn extension(&self) -> u8 {
|
||||
self.cookie.extension as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn evtype(&self) -> c_int {
|
||||
self.cookie.evtype
|
||||
}
|
||||
|
||||
/// Borrow inner event data as `&T`.
|
||||
///
|
||||
/// ## SAFETY
|
||||
///
|
||||
/// The caller must ensure that the event has the `T` inside of it.
|
||||
#[inline]
|
||||
pub unsafe fn as_event<T>(&self) -> &T {
|
||||
unsafe { &*(self.cookie.data as *const _) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GenericEventCookie {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::ffi::CString;
|
||||
use std::iter;
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
|
||||
@@ -56,10 +57,22 @@ impl XConnection {
|
||||
None => return self.create_empty_cursor(),
|
||||
};
|
||||
|
||||
let name = CString::new(cursor.name()).unwrap();
|
||||
unsafe {
|
||||
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
|
||||
let mut xcursor = 0;
|
||||
for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
|
||||
let name = CString::new(name).unwrap();
|
||||
xcursor = unsafe {
|
||||
(self.xcursor.XcursorLibraryLoadCursor)(
|
||||
self.display,
|
||||
name.as_ptr() as *const c_char,
|
||||
)
|
||||
};
|
||||
|
||||
if xcursor != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
xcursor
|
||||
}
|
||||
|
||||
fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> {
|
||||
|
||||
@@ -83,14 +83,6 @@ impl FrameExtents {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LogicalFrameExtents {
|
||||
pub left: f64,
|
||||
pub right: f64,
|
||||
pub top: f64,
|
||||
pub bottom: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FrameExtentsHeuristicPath {
|
||||
Supported,
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
// Welcome to the util module, where we try to keep you from shooting yourself in the foot.
|
||||
// *results may vary
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::BitAnd,
|
||||
os::raw::*,
|
||||
};
|
||||
|
||||
mod client_msg;
|
||||
pub mod cookie;
|
||||
mod cursor;
|
||||
mod geometry;
|
||||
mod hint;
|
||||
@@ -9,18 +16,14 @@ mod icon;
|
||||
mod input;
|
||||
pub mod keys;
|
||||
pub(crate) mod memory;
|
||||
mod mouse;
|
||||
mod randr;
|
||||
mod window_property;
|
||||
mod wm;
|
||||
mod xmodmap;
|
||||
|
||||
pub use self::{
|
||||
client_msg::*, geometry::*, hint::*, icon::*, input::*, randr::*, window_property::*, wm::*,
|
||||
};
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::BitAnd,
|
||||
os::raw::*,
|
||||
geometry::*, hint::*, input::*, mouse::*, window_property::*, wm::*, xmodmap::ModifierKeymap,
|
||||
};
|
||||
|
||||
use super::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError};
|
||||
|
||||
52
src/platform_impl/linux/x11/util/mouse.rs
Normal file
52
src/platform_impl/linux/x11/util/mouse.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
//! Utilities for handling mouse events.
|
||||
|
||||
/// Recorded mouse delta designed to filter out noise.
|
||||
pub struct Delta<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Delta<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
x: Default::default(),
|
||||
y: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Delta<T> {
|
||||
pub(crate) fn set_x(&mut self, x: T) {
|
||||
self.x = x;
|
||||
}
|
||||
|
||||
pub(crate) fn set_y(&mut self, y: T) {
|
||||
self.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! consume {
|
||||
($this:expr, $ty:ty) => {{
|
||||
let this = $this;
|
||||
let (x, y) = match (this.x.abs() < <$ty>::EPSILON, this.y.abs() < <$ty>::EPSILON) {
|
||||
(true, true) => return None,
|
||||
(false, true) => (this.x, 0.0),
|
||||
(true, false) => (0.0, this.y),
|
||||
(false, false) => (this.x, this.y),
|
||||
};
|
||||
|
||||
Some((x, y))
|
||||
}};
|
||||
}
|
||||
|
||||
impl Delta<f32> {
|
||||
pub(crate) fn consume(self) -> Option<(f32, f32)> {
|
||||
consume!(self, f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Delta<f64> {
|
||||
pub(crate) fn consume(self) -> Option<(f64, f64)> {
|
||||
consume!(self, f64)
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,19 @@ pub fn calc_dpi_factor(
|
||||
impl XConnection {
|
||||
// Retrieve DPI from Xft.dpi property
|
||||
pub fn get_xft_dpi(&self) -> Option<f64> {
|
||||
// Try to get it from XSETTINGS first.
|
||||
if let Some(xsettings_screen) = self.xsettings_screen() {
|
||||
match self.xsettings_dpi(xsettings_screen) {
|
||||
Ok(Some(dpi)) => return Some(dpi),
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to fetch XSettings: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.database()
|
||||
.get_string("Xfi.dpi", "")
|
||||
.get_string("Xft.dpi", "")
|
||||
.and_then(|s| f64::from_str(s).ok())
|
||||
}
|
||||
pub fn get_output_info(
|
||||
|
||||
56
src/platform_impl/linux/x11/util/xmodmap.rs
Normal file
56
src/platform_impl/linux/x11/util/xmodmap.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::collections::HashSet;
|
||||
use std::slice;
|
||||
|
||||
use x11_dl::xlib::{KeyCode as XKeyCode, XModifierKeymap};
|
||||
|
||||
// Offsets within XModifierKeymap to each set of keycodes.
|
||||
// We are only interested in Shift, Control, Alt, and Logo.
|
||||
//
|
||||
// There are 8 sets total. The order of keycode sets is:
|
||||
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
|
||||
//
|
||||
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
|
||||
const NUM_MODS: usize = 8;
|
||||
|
||||
/// Track which keys are modifiers, so we can properly replay them when they were filtered.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ModifierKeymap {
|
||||
// Maps keycodes to modifiers
|
||||
modifers: HashSet<XKeyCode>,
|
||||
}
|
||||
|
||||
impl ModifierKeymap {
|
||||
pub fn new() -> ModifierKeymap {
|
||||
ModifierKeymap::default()
|
||||
}
|
||||
|
||||
pub fn is_modifier(&self, keycode: XKeyCode) -> bool {
|
||||
self.modifers.contains(&keycode)
|
||||
}
|
||||
|
||||
pub fn reload_from_x_connection(&mut self, xconn: &super::XConnection) {
|
||||
unsafe {
|
||||
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
|
||||
|
||||
if keymap.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.reset_from_x_keymap(&*keymap);
|
||||
|
||||
(xconn.xlib.XFreeModifiermap)(keymap);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_from_x_keymap(&mut self, keymap: &XModifierKeymap) {
|
||||
let keys_per_mod = keymap.max_keypermod as usize;
|
||||
|
||||
let keys = unsafe {
|
||||
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
|
||||
};
|
||||
self.modifers.clear();
|
||||
for key in keys {
|
||||
self.modifers.insert(*key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
|
||||
properties::{WmHints, WmSizeHints, WmSizeHintsSpecification},
|
||||
protocol::{
|
||||
randr,
|
||||
shape::SK,
|
||||
@@ -22,9 +22,13 @@ use x11rb::{
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, InnerSizeWriter, WindowEvent},
|
||||
event_loop::AsyncRequestSerial,
|
||||
platform_impl::{
|
||||
x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error},
|
||||
x11::{
|
||||
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
|
||||
X11Error,
|
||||
},
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||
},
|
||||
@@ -114,7 +118,7 @@ impl SharedState {
|
||||
unsafe impl Send for UnownedWindow {}
|
||||
unsafe impl Sync for UnownedWindow {}
|
||||
|
||||
pub(crate) struct UnownedWindow {
|
||||
pub struct UnownedWindow {
|
||||
pub(crate) xconn: Arc<XConnection>, // never changes
|
||||
xwindow: xproto::Window, // never changes
|
||||
#[allow(dead_code)]
|
||||
@@ -276,7 +280,8 @@ impl UnownedWindow {
|
||||
| EventMask::KEYMAP_STATE
|
||||
| EventMask::BUTTON_PRESS
|
||||
| EventMask::BUTTON_RELEASE
|
||||
| EventMask::POINTER_MOTION;
|
||||
| EventMask::POINTER_MOTION
|
||||
| EventMask::PROPERTY_CHANGE;
|
||||
|
||||
aux = aux.event_mask(event_mask).border_pixel(0);
|
||||
|
||||
@@ -391,7 +396,7 @@ impl UnownedWindow {
|
||||
|
||||
// WM_CLASS must be set *before* mapping the window, as per ICCCM!
|
||||
{
|
||||
let (class, instance) = if let Some(name) = pl_attribs.name {
|
||||
let (instance, class) = if let Some(name) = pl_attribs.name {
|
||||
(name.instance, name.general)
|
||||
} else {
|
||||
let class = env::args_os()
|
||||
@@ -536,9 +541,9 @@ impl UnownedWindow {
|
||||
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
|
||||
.ignore_error();
|
||||
|
||||
{
|
||||
let result = event_loop
|
||||
.ime
|
||||
// Try to create input context for the window.
|
||||
if let Some(ime) = event_loop.ime.as_ref() {
|
||||
let result = ime
|
||||
.borrow_mut()
|
||||
.create_context(window.xwindow as ffi::Window, false);
|
||||
leap!(result);
|
||||
@@ -923,6 +928,51 @@ impl UnownedWindow {
|
||||
})
|
||||
}
|
||||
|
||||
/// Refresh the API for the given monitor.
|
||||
#[inline]
|
||||
pub(super) fn refresh_dpi_for_monitor<T: 'static>(
|
||||
&self,
|
||||
new_monitor: &X11MonitorHandle,
|
||||
maybe_prev_scale_factor: Option<f64>,
|
||||
mut callback: impl FnMut(Event<T>),
|
||||
) {
|
||||
// Check if the self is on this monitor
|
||||
let monitor = self.shared_state_lock().last_monitor.clone();
|
||||
if monitor.name == new_monitor.name {
|
||||
let (width, height) = self.inner_size_physical();
|
||||
let (new_width, new_height) = self.adjust_for_dpi(
|
||||
// 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,
|
||||
&self.shared_state_lock(),
|
||||
);
|
||||
|
||||
let window_id = crate::window::WindowId(self.id());
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_monitor.scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)),
|
||||
},
|
||||
});
|
||||
|
||||
let new_inner_size = *inner_size.lock().unwrap();
|
||||
drop(inner_size);
|
||||
|
||||
if new_inner_size != old_inner_size {
|
||||
let (new_width, new_height) = new_inner_size.into();
|
||||
self.request_inner_size_physical(new_width, new_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_minimized_inner(&self, minimized: bool) -> Result<VoidCookie<'_>, X11Error> {
|
||||
let atoms = self.xconn.atoms();
|
||||
|
||||
@@ -937,7 +987,7 @@ impl UnownedWindow {
|
||||
xproto::EventMask::SUBSTRUCTURE_REDIRECT
|
||||
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
),
|
||||
[WmHintsState::Iconic as u32, 0, 0, 0, 0],
|
||||
[3u32, 0, 0, 0, 0],
|
||||
)
|
||||
} else {
|
||||
self.xconn.send_client_msg(
|
||||
@@ -1327,7 +1377,8 @@ impl UnownedWindow {
|
||||
self.xwindow as xproto::Window,
|
||||
xproto::AtomEnum::WM_NORMAL_HINTS,
|
||||
)?
|
||||
.reply()?;
|
||||
.reply()?
|
||||
.unwrap_or_default();
|
||||
callback(&mut normal_hints);
|
||||
normal_hints
|
||||
.set(
|
||||
@@ -1380,6 +1431,7 @@ impl UnownedWindow {
|
||||
)
|
||||
.ok()
|
||||
.and_then(|cookie| cookie.reply().ok())
|
||||
.flatten()
|
||||
.and_then(|hints| hints.size_increment)
|
||||
.map(|(width, height)| (width as u32, height as u32).into())
|
||||
}
|
||||
@@ -1692,8 +1744,8 @@ impl UnownedWindow {
|
||||
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
),
|
||||
[
|
||||
(window.x as u32 + pointer.win_x as u32),
|
||||
(window.y as u32 + pointer.win_y as u32),
|
||||
(window.x as u32 + xinput_fp1616_to_float(pointer.win_x) as u32),
|
||||
(window.y as u32 + xinput_fp1616_to_float(pointer.win_y) as u32),
|
||||
action.try_into().unwrap(),
|
||||
1, // Button 1
|
||||
1,
|
||||
@@ -1735,9 +1787,9 @@ impl UnownedWindow {
|
||||
let state_type_atom = atoms[CARD32];
|
||||
let is_minimized = if let Ok(state) =
|
||||
self.xconn
|
||||
.get_property(self.xwindow, state_atom, state_type_atom)
|
||||
.get_property::<u32>(self.xwindow, state_atom, state_type_atom)
|
||||
{
|
||||
state.contains(&(ffi::IconicState as c_ulong))
|
||||
state.contains(&super::ICONIC_STATE)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -1774,6 +1826,7 @@ impl UnownedWindow {
|
||||
WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
|
||||
.ok()
|
||||
.and_then(|cookie| cookie.reply().ok())
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
wm_hints.urgent = request_type.is_some();
|
||||
|
||||
@@ -4,17 +4,25 @@ use std::{
|
||||
fmt, ptr,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc, Mutex,
|
||||
Arc, Mutex, RwLock, RwLockReadGuard,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
|
||||
use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
protocol::{
|
||||
randr::ConnectionExt as _,
|
||||
xproto::{self, ConnectionExt},
|
||||
},
|
||||
resource_manager,
|
||||
xcb_ffi::XCBConnection,
|
||||
};
|
||||
|
||||
/// A connection to an X server.
|
||||
pub(crate) struct XConnection {
|
||||
pub struct XConnection {
|
||||
pub xlib: ffi::Xlib,
|
||||
pub xcursor: ffi::Xcursor,
|
||||
|
||||
@@ -45,7 +53,13 @@ pub(crate) struct XConnection {
|
||||
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
|
||||
|
||||
/// The resource database.
|
||||
database: resource_manager::Database,
|
||||
database: RwLock<resource_manager::Database>,
|
||||
|
||||
/// RandR version.
|
||||
randr_version: (u32, u32),
|
||||
|
||||
/// Atom for the XSettings screen.
|
||||
xsettings_screen: Option<xproto::Atom>,
|
||||
|
||||
pub latest_error: Mutex<Option<XError>>,
|
||||
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
|
||||
@@ -94,16 +108,28 @@ impl XConnection {
|
||||
// Get the default screen.
|
||||
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
|
||||
|
||||
// Fetch the atoms.
|
||||
// Load the database.
|
||||
let database = resource_manager::new_from_default(&xcb)
|
||||
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
|
||||
|
||||
// Load the RandR version.
|
||||
let randr_version = xcb
|
||||
.randr_query_version(1, 3)
|
||||
.expect("failed to request XRandR version")
|
||||
.reply()
|
||||
.expect("failed to query XRandR version");
|
||||
|
||||
let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen);
|
||||
if xsettings_screen.is_none() {
|
||||
log::warn!("error setting XSETTINGS; Xft options won't reload automatically")
|
||||
}
|
||||
|
||||
// Fetch atoms.
|
||||
let atoms = Atoms::new(&xcb)
|
||||
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
|
||||
.reply()
|
||||
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
|
||||
|
||||
// Load the database.
|
||||
let database = resource_manager::new_from_default(&xcb)
|
||||
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
|
||||
|
||||
Ok(XConnection {
|
||||
xlib,
|
||||
xcursor,
|
||||
@@ -115,11 +141,44 @@ impl XConnection {
|
||||
timestamp: AtomicU32::new(0),
|
||||
latest_error: Mutex::new(None),
|
||||
monitor_handles: Mutex::new(None),
|
||||
database,
|
||||
database: RwLock::new(database),
|
||||
cursor_cache: Default::default(),
|
||||
randr_version: (randr_version.major_version, randr_version.minor_version),
|
||||
xsettings_screen,
|
||||
})
|
||||
}
|
||||
|
||||
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
|
||||
// Fetch the _XSETTINGS_S[screen number] atom.
|
||||
let xsettings_screen = xcb
|
||||
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
|
||||
.ok()?
|
||||
.reply()
|
||||
.ok()?
|
||||
.atom;
|
||||
|
||||
// Get PropertyNotify events from the XSETTINGS window.
|
||||
// TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window
|
||||
// in order to accomodate for a changed window here.
|
||||
let selector_window = xcb
|
||||
.get_selection_owner(xsettings_screen)
|
||||
.ok()?
|
||||
.reply()
|
||||
.ok()?
|
||||
.owner;
|
||||
|
||||
xcb.change_window_attributes(
|
||||
selector_window,
|
||||
&xproto::ChangeWindowAttributesAux::new()
|
||||
.event_mask(xproto::EventMask::PROPERTY_CHANGE),
|
||||
)
|
||||
.ok()?
|
||||
.check()
|
||||
.ok()?;
|
||||
|
||||
Some(xsettings_screen)
|
||||
}
|
||||
|
||||
/// Checks whether an error has been triggered by the previous function calls.
|
||||
#[inline]
|
||||
pub fn check_errors(&self) -> Result<(), XError> {
|
||||
@@ -131,6 +190,11 @@ impl XConnection {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn randr_version(&self) -> (u32, u32) {
|
||||
self.randr_version
|
||||
}
|
||||
|
||||
/// Get the underlying XCB connection.
|
||||
#[inline]
|
||||
pub fn xcb_connection(&self) -> &XCBConnection {
|
||||
@@ -159,8 +223,16 @@ impl XConnection {
|
||||
|
||||
/// Get the resource database.
|
||||
#[inline]
|
||||
pub fn database(&self) -> &resource_manager::Database {
|
||||
&self.database
|
||||
pub fn database(&self) -> RwLockReadGuard<'_, resource_manager::Database> {
|
||||
self.database.read().unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
/// Reload the resource database.
|
||||
#[inline]
|
||||
pub fn reload_database(&self) -> Result<(), super::X11Error> {
|
||||
let database = resource_manager::new_from_default(self.xcb_connection())?;
|
||||
*self.database.write().unwrap_or_else(|e| e.into_inner()) = database;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the latest timestamp.
|
||||
@@ -192,6 +264,12 @@ impl XConnection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the atom for Xsettings.
|
||||
#[inline]
|
||||
pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
|
||||
self.xsettings_screen
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for XConnection {
|
||||
|
||||
345
src/platform_impl/linux/x11/xsettings.rs
Normal file
345
src/platform_impl/linux/x11/xsettings.rs
Normal file
@@ -0,0 +1,345 @@
|
||||
//! Parser for the xsettings data format.
|
||||
//!
|
||||
//! Some of this code is referenced from [here].
|
||||
//!
|
||||
//! [here]: https://github.com/derat/xsettingsd
|
||||
|
||||
use std::iter;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt};
|
||||
|
||||
use super::{atoms::*, XConnection};
|
||||
|
||||
type Result<T> = core::result::Result<T, ParserError>;
|
||||
|
||||
const DPI_NAME: &[u8] = b"Xft/DPI";
|
||||
const DPI_MULTIPLIER: f64 = 1024.0;
|
||||
const LITTLE_ENDIAN: u8 = b'l';
|
||||
const BIG_ENDIAN: u8 = b'B';
|
||||
|
||||
impl XConnection {
|
||||
/// Get the DPI from XSettings.
|
||||
pub(crate) fn xsettings_dpi(
|
||||
&self,
|
||||
xsettings_screen: xproto::Atom,
|
||||
) -> core::result::Result<Option<f64>, super::X11Error> {
|
||||
let atoms = self.atoms();
|
||||
|
||||
// Get the current owner of the screen's settings.
|
||||
let owner = self
|
||||
.xcb_connection()
|
||||
.get_selection_owner(xsettings_screen)?
|
||||
.reply()?;
|
||||
|
||||
// Read the _XSETTINGS_SETTINGS property.
|
||||
let data: Vec<u8> = self
|
||||
.get_property(
|
||||
owner.owner,
|
||||
atoms[_XSETTINGS_SETTINGS],
|
||||
atoms[_XSETTINGS_SETTINGS],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Parse the property.
|
||||
let dpi_setting = read_settings(&data)?
|
||||
.find(|res| res.as_ref().map_or(true, |s| s.name == DPI_NAME))
|
||||
.transpose()?;
|
||||
if let Some(dpi_setting) = dpi_setting {
|
||||
let base_dpi = match dpi_setting.data {
|
||||
SettingData::Integer(dpi) => dpi as f64,
|
||||
SettingData::String(_) => {
|
||||
return Err(ParserError::BadType(SettingType::String).into())
|
||||
}
|
||||
SettingData::Color(_) => {
|
||||
return Err(ParserError::BadType(SettingType::Color).into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(base_dpi / DPI_MULTIPLIER))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read over the settings in the block of data.
|
||||
fn read_settings(data: &[u8]) -> Result<impl Iterator<Item = Result<Setting<'_>>> + '_> {
|
||||
// Create a parser. This automatically parses the first 8 bytes for metadata.
|
||||
let mut parser = Parser::new(data)?;
|
||||
|
||||
// Read the total number of settings.
|
||||
let total_settings = parser.i32()?;
|
||||
|
||||
// Iterate over the settings.
|
||||
let iter = iter::repeat_with(move || Setting::parse(&mut parser)).take(total_settings as usize);
|
||||
Ok(iter)
|
||||
}
|
||||
|
||||
/// A setting in the settings list.
|
||||
struct Setting<'a> {
|
||||
/// The name of the setting.
|
||||
name: &'a [u8],
|
||||
|
||||
/// The data contained in the setting.
|
||||
data: SettingData<'a>,
|
||||
}
|
||||
|
||||
/// The data contained in a setting.
|
||||
enum SettingData<'a> {
|
||||
Integer(i32),
|
||||
String(#[allow(dead_code)] &'a [u8]),
|
||||
Color(#[allow(dead_code)] [i16; 4]),
|
||||
}
|
||||
|
||||
impl<'a> Setting<'a> {
|
||||
/// Parse a new `SettingData`.
|
||||
fn parse(parser: &mut Parser<'a>) -> Result<Self> {
|
||||
// Read the type.
|
||||
let ty: SettingType = parser.i8()?.try_into()?;
|
||||
|
||||
// Read another byte of padding.
|
||||
parser.advance(1)?;
|
||||
|
||||
// Read the name of the setting.
|
||||
let name_len = parser.i16()?;
|
||||
let name = parser.advance(name_len as usize)?;
|
||||
parser.pad(name.len(), 4)?;
|
||||
|
||||
// Ignore the serial number.
|
||||
parser.advance(4)?;
|
||||
|
||||
let data = match ty {
|
||||
SettingType::Integer => {
|
||||
// Read a 32-bit integer.
|
||||
SettingData::Integer(parser.i32()?)
|
||||
}
|
||||
|
||||
SettingType::String => {
|
||||
// Read the data.
|
||||
let data_len = parser.i32()?;
|
||||
let data = parser.advance(data_len as usize)?;
|
||||
parser.pad(data.len(), 4)?;
|
||||
|
||||
SettingData::String(data)
|
||||
}
|
||||
|
||||
SettingType::Color => {
|
||||
// Read i16's of color.
|
||||
let (red, blue, green, alpha) =
|
||||
(parser.i16()?, parser.i16()?, parser.i16()?, parser.i16()?);
|
||||
|
||||
SettingData::Color([red, blue, green, alpha])
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Setting { name, data })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SettingType {
|
||||
Integer = 0,
|
||||
String = 1,
|
||||
Color = 2,
|
||||
}
|
||||
|
||||
impl TryFrom<i8> for SettingType {
|
||||
type Error = ParserError;
|
||||
|
||||
fn try_from(value: i8) -> Result<Self> {
|
||||
Ok(match value {
|
||||
0 => Self::Integer,
|
||||
1 => Self::String,
|
||||
2 => Self::Color,
|
||||
x => return Err(ParserError::InvalidType(x)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parser for the incoming byte stream.
|
||||
struct Parser<'a> {
|
||||
bytes: &'a [u8],
|
||||
endianness: Endianness,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
/// Create a new parser.
|
||||
fn new(bytes: &'a [u8]) -> Result<Self> {
|
||||
let (endianness, bytes) = bytes
|
||||
.split_first()
|
||||
.ok_or_else(|| ParserError::ran_out(1, 0))?;
|
||||
let endianness = match *endianness {
|
||||
BIG_ENDIAN => Endianness::Big,
|
||||
LITTLE_ENDIAN => Endianness::Little,
|
||||
_ => Endianness::native(),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
// Ignore three bytes of padding and the four-byte serial.
|
||||
bytes: bytes
|
||||
.get(7..)
|
||||
.ok_or_else(|| ParserError::ran_out(7, bytes.len()))?,
|
||||
endianness,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a slice of bytes.
|
||||
fn advance(&mut self, n: usize) -> Result<&'a [u8]> {
|
||||
if n == 0 {
|
||||
return Ok(&[]);
|
||||
}
|
||||
|
||||
if n > self.bytes.len() {
|
||||
Err(ParserError::ran_out(n, self.bytes.len()))
|
||||
} else {
|
||||
let (part, rem) = self.bytes.split_at(n);
|
||||
self.bytes = rem;
|
||||
Ok(part)
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip some padding.
|
||||
fn pad(&mut self, size: usize, pad: usize) -> Result<()> {
|
||||
let advance = (pad - (size % pad)) % pad;
|
||||
self.advance(advance)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a single byte.
|
||||
fn i8(&mut self) -> Result<i8> {
|
||||
self.advance(1).map(|s| s[0] as i8)
|
||||
}
|
||||
|
||||
/// Get two bytes.
|
||||
fn i16(&mut self) -> Result<i16> {
|
||||
self.advance(2).map(|s| {
|
||||
let bytes: &[u8; 2] = s.try_into().unwrap();
|
||||
match self.endianness {
|
||||
Endianness::Big => i16::from_be_bytes(*bytes),
|
||||
Endianness::Little => i16::from_le_bytes(*bytes),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get four bytes.
|
||||
fn i32(&mut self) -> Result<i32> {
|
||||
self.advance(4).map(|s| {
|
||||
let bytes: &[u8; 4] = s.try_into().unwrap();
|
||||
match self.endianness {
|
||||
Endianness::Big => i32::from_be_bytes(*bytes),
|
||||
Endianness::Little => i32::from_le_bytes(*bytes),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Endianness of the incoming data.
|
||||
enum Endianness {
|
||||
Little,
|
||||
Big,
|
||||
}
|
||||
|
||||
impl Endianness {
|
||||
#[cfg(target_endian = "little")]
|
||||
fn native() -> Self {
|
||||
Endianness::Little
|
||||
}
|
||||
|
||||
#[cfg(target_endian = "big")]
|
||||
fn native() -> Self {
|
||||
Endianness::Big
|
||||
}
|
||||
}
|
||||
|
||||
/// Parser errors.
|
||||
#[derive(Debug)]
|
||||
pub enum ParserError {
|
||||
/// Ran out of bytes.
|
||||
NoMoreBytes {
|
||||
expected: NonZeroUsize,
|
||||
found: usize,
|
||||
},
|
||||
|
||||
/// Invalid type.
|
||||
InvalidType(i8),
|
||||
|
||||
/// Bad setting type.
|
||||
BadType(SettingType),
|
||||
}
|
||||
|
||||
impl ParserError {
|
||||
fn ran_out(expected: usize, found: usize) -> ParserError {
|
||||
let expected = NonZeroUsize::new(expected).unwrap();
|
||||
Self::NoMoreBytes { expected, found }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//! Tests for the XSETTINGS parser.
|
||||
|
||||
use super::*;
|
||||
|
||||
const XSETTINGS: &str = include_str!("tests/xsettings.dat");
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let err = match read_settings(&[]) {
|
||||
Ok(_) => panic!(),
|
||||
Err(err) => err,
|
||||
};
|
||||
match err {
|
||||
ParserError::NoMoreBytes { expected, found } => {
|
||||
assert_eq!(expected.get(), 1);
|
||||
assert_eq!(found, 0);
|
||||
}
|
||||
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_xsettings() {
|
||||
let data = XSETTINGS
|
||||
.trim()
|
||||
.split(',')
|
||||
.map(|tok| {
|
||||
let val = tok.strip_prefix("0x").unwrap();
|
||||
u8::from_str_radix(val, 16).unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let settings = read_settings(&data)
|
||||
.unwrap()
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.unwrap();
|
||||
|
||||
let dpi = settings.iter().find(|s| s.name == b"Xft/DPI").unwrap();
|
||||
assert_int(&dpi.data, 96 * 1024);
|
||||
let hinting = settings.iter().find(|s| s.name == b"Xft/Hinting").unwrap();
|
||||
assert_int(&hinting.data, 1);
|
||||
|
||||
let rgba = settings.iter().find(|s| s.name == b"Xft/RGBA").unwrap();
|
||||
assert_string(&rgba.data, "rgb");
|
||||
let lcd = settings
|
||||
.iter()
|
||||
.find(|s| s.name == b"Xft/Lcdfilter")
|
||||
.unwrap();
|
||||
assert_string(&lcd.data, "lcddefault");
|
||||
}
|
||||
|
||||
fn assert_string(dat: &SettingData<'_>, s: &str) {
|
||||
match dat {
|
||||
SettingData::String(left) => assert_eq!(*left, s.as_bytes()),
|
||||
_ => panic!("invalid data type"),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_int(dat: &SettingData<'_>, i: i32) {
|
||||
match dat {
|
||||
SettingData::Integer(left) => assert_eq!(*left, i),
|
||||
_ => panic!("invalid data type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,6 +209,10 @@ impl Handler {
|
||||
self.exit.store(true, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn clear_exit(&self) {
|
||||
self.exit.store(false, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn exiting(&self) -> bool {
|
||||
self.exit.load(Ordering::Relaxed)
|
||||
}
|
||||
@@ -329,7 +333,7 @@ impl Handler {
|
||||
) {
|
||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
||||
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
||||
let event = Event::WindowEvent {
|
||||
let scale_factor_changed_event = Event::WindowEvent {
|
||||
window_id: WindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
@@ -337,13 +341,19 @@ impl Handler {
|
||||
},
|
||||
};
|
||||
|
||||
callback.handle_nonuser_event(event);
|
||||
callback.handle_nonuser_event(scale_factor_changed_event);
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let logical_size = physical_size.to_logical(scale_factor);
|
||||
let size = NSSize::new(logical_size.width, logical_size.height);
|
||||
window.setContentSize(size);
|
||||
|
||||
let resized_event = Event::WindowEvent {
|
||||
window_id: WindowId(window.id()),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
};
|
||||
callback.handle_nonuser_event(resized_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,6 +438,10 @@ impl AppState {
|
||||
HANDLER.exit()
|
||||
}
|
||||
|
||||
pub fn clear_exit() {
|
||||
HANDLER.clear_exit()
|
||||
}
|
||||
|
||||
pub fn exiting() -> bool {
|
||||
HANDLER.exiting()
|
||||
}
|
||||
@@ -494,9 +508,9 @@ impl AppState {
|
||||
|
||||
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
|
||||
if panic_info.is_panicking()
|
||||
|| HANDLER.get_in_callback()
|
||||
|| !HANDLER.have_callback()
|
||||
|| !HANDLER.is_running()
|
||||
|| HANDLER.get_in_callback()
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -601,9 +615,9 @@ impl AppState {
|
||||
// XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're
|
||||
// about to return to the `CFRunLoop` to poll for new events?
|
||||
if panic_info.is_panicking()
|
||||
|| HANDLER.get_in_callback()
|
||||
|| !HANDLER.have_callback()
|
||||
|| !HANDLER.is_running()
|
||||
|| HANDLER.get_in_callback()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ extern_methods!(
|
||||
#[method(frame)]
|
||||
pub(crate) fn frame(&self) -> NSRect;
|
||||
|
||||
#[method(windowNumber)]
|
||||
pub(crate) fn windowNumber(&self) -> NSInteger;
|
||||
|
||||
#[method(backingScaleFactor)]
|
||||
pub(crate) fn backingScaleFactor(&self) -> CGFloat;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ignores ALL modifiers.
|
||||
pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
let mut string = [0; 16];
|
||||
let input_source;
|
||||
@@ -88,13 +89,16 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
if result_len == 0 {
|
||||
log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length.");
|
||||
// This is fine - not all keys have text representation.
|
||||
// For instance, users that have mapped the `Fn` key to toggle
|
||||
// keyboard layouts will hit this code path.
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
let chars = String::from_utf16_lossy(&string[0..result_len as usize]);
|
||||
Key::Character(SmolStr::new(chars))
|
||||
}
|
||||
|
||||
// Ignores all modifiers except for SHIFT (yes, even ALT is ignored).
|
||||
fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
|
||||
let string = ns_event
|
||||
.charactersIgnoringModifiers()
|
||||
@@ -124,6 +128,13 @@ pub(crate) fn create_key_event(
|
||||
let mut physical_key =
|
||||
key_override.unwrap_or_else(|| PhysicalKey::from_scancode(scancode as u32));
|
||||
|
||||
// NOTE: The logical key should heed both SHIFT and ALT if possible.
|
||||
// For instance:
|
||||
// * Pressing the A key: logical key should be "a"
|
||||
// * Pressing SHIFT A: logical key should be "A"
|
||||
// * Pressing CTRL SHIFT A: logical key should also be "A"
|
||||
// This is not easy to tease out of `NSEvent`, but we do our best.
|
||||
|
||||
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
|
||||
None
|
||||
} else {
|
||||
@@ -144,25 +155,31 @@ pub(crate) fn create_key_event(
|
||||
|
||||
let key_from_code = code_to_key(physical_key, scancode);
|
||||
let (logical_key, key_without_modifiers) = if matches!(key_from_code, Key::Unidentified(_)) {
|
||||
// `get_modifierless_char/key_without_modifiers` ignores ALL modifiers.
|
||||
let key_without_modifiers = get_modifierless_char(scancode);
|
||||
|
||||
let modifiers = NSEvent::modifierFlags(ns_event);
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
|
||||
let logical_key = match text_with_all_modifiers.as_ref() {
|
||||
// Only checking for ctrl here, not checking for alt because we DO want to
|
||||
// Only checking for ctrl and cmd here, not checking for alt because we DO want to
|
||||
// include its effect in the key. For example if -on the Germay layout- one
|
||||
// presses alt+8, the logical key should be "{"
|
||||
// Also not checking if this is a release event because then this issue would
|
||||
// still affect the key release.
|
||||
Some(text) if !has_ctrl => Key::Character(text.clone()),
|
||||
_ => {
|
||||
let modifierless_chars = match key_without_modifiers.as_ref() {
|
||||
Key::Character(ch) => ch,
|
||||
_ => "",
|
||||
};
|
||||
get_logical_key_char(ns_event, modifierless_chars)
|
||||
Some(text) if !has_ctrl && !has_cmd => {
|
||||
// Character heeding both SHIFT and ALT.
|
||||
Key::Character(text.clone())
|
||||
}
|
||||
|
||||
_ => match key_without_modifiers.as_ref() {
|
||||
// Character heeding just SHIFT, ignoring ALT.
|
||||
Key::Character(ch) => get_logical_key_char(ns_event, ch),
|
||||
|
||||
// Character ignoring ALL modifiers.
|
||||
_ => key_without_modifiers.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
(logical_key, key_without_modifiers)
|
||||
|
||||
@@ -116,6 +116,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
AppState::exit()
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
AppState::clear_exit()
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
AppState::exiting()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use core_graphics::{
|
||||
base::CGError,
|
||||
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
||||
};
|
||||
use objc2::{ffi::NSInteger, runtime::AnyObject};
|
||||
|
||||
pub type CGDisplayFadeInterval = f32;
|
||||
pub type CGDisplayReservationInterval = f32;
|
||||
@@ -113,6 +114,14 @@ extern "C" {
|
||||
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
|
||||
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
|
||||
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
|
||||
|
||||
// Wildly used private APIs; Apple uses them for their Terminal.app.
|
||||
pub fn CGSMainConnectionID() -> *mut AnyObject;
|
||||
pub fn CGSSetWindowBackgroundBlurRadius(
|
||||
connection_id: *mut AnyObject,
|
||||
window_id: NSInteger,
|
||||
radius: i64,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
mod core_video {
|
||||
|
||||
@@ -7,7 +7,9 @@ use core_foundation::{
|
||||
base::{CFRelease, TCFType},
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
|
||||
use super::appkit::NSScreen;
|
||||
@@ -216,6 +218,12 @@ impl MonitorHandle {
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
unsafe {
|
||||
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
|
||||
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
|
||||
if refresh_rate > 0.0 {
|
||||
return Some((refresh_rate * 1000.0).round() as u32);
|
||||
}
|
||||
|
||||
let mut display_link = std::ptr::null_mut();
|
||||
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
|
||||
!= ffi::kCVReturnSuccess
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
self,
|
||||
ffi::c_void,
|
||||
panic::{AssertUnwindSafe, UnwindSafe},
|
||||
ptr,
|
||||
|
||||
@@ -74,8 +74,8 @@ enum ImeState {
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
struct ModLocationMask: u8 {
|
||||
const LEFT = 1;
|
||||
const RIGHT = 2;
|
||||
const LEFT = 0b0001;
|
||||
const RIGHT = 0b0010;
|
||||
}
|
||||
}
|
||||
impl ModLocationMask {
|
||||
@@ -88,13 +88,13 @@ impl ModLocationMask {
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_modifier(key: &Key) -> ModifiersState {
|
||||
fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
|
||||
match key {
|
||||
Key::Named(NamedKey::Alt) => ModifiersState::ALT,
|
||||
Key::Named(NamedKey::Control) => ModifiersState::CONTROL,
|
||||
Key::Named(NamedKey::Super) => ModifiersState::SUPER,
|
||||
Key::Named(NamedKey::Shift) => ModifiersState::SHIFT,
|
||||
_ => unreachable!(),
|
||||
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
|
||||
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
|
||||
Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
|
||||
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,9 +410,9 @@ declare_class!(
|
||||
let content_rect = window.contentRectForFrameRect(window.frame());
|
||||
let base_x = content_rect.origin.x as f64;
|
||||
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
|
||||
let x = base_x + self.state.ime_position.get().x;
|
||||
let y = base_y - self.state.ime_position.get().y;
|
||||
let LogicalSize { width, height } = self.state.ime_size.get();
|
||||
let x = base_x + self.state.ime_position.get().x;
|
||||
let y = base_y - self.state.ime_position.get().y - height;
|
||||
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height))
|
||||
}
|
||||
|
||||
@@ -924,91 +924,96 @@ impl WinitView {
|
||||
// event, thus we can't generate regular presses based on that. The `ModifiersChanged`
|
||||
// later will work though, since the flags are attached to the event and contain valid
|
||||
// information.
|
||||
if is_flags_changed_event && ns_event.key_code() != 0 {
|
||||
let scancode = ns_event.key_code();
|
||||
let physical_key = PhysicalKey::from_scancode(scancode as u32);
|
||||
'send_event: {
|
||||
if is_flags_changed_event && ns_event.key_code() != 0 {
|
||||
let scancode = ns_event.key_code();
|
||||
let physical_key = PhysicalKey::from_scancode(scancode as u32);
|
||||
|
||||
// We'll correct the `is_press` later.
|
||||
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
|
||||
// We'll correct the `is_press` later.
|
||||
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
|
||||
|
||||
let key = code_to_key(physical_key, scancode);
|
||||
let event_modifier = key_to_modifier(&key);
|
||||
event.physical_key = physical_key;
|
||||
event.logical_key = key.clone();
|
||||
event.location = code_to_location(physical_key);
|
||||
let location_mask = ModLocationMask::from_location(event.location);
|
||||
let key = code_to_key(physical_key, scancode);
|
||||
// Ignore processing of unkown modifiers because we can't determine whether
|
||||
// it was pressed or release reliably.
|
||||
let Some(event_modifier) = key_to_modifier(&key) else {
|
||||
break 'send_event;
|
||||
};
|
||||
event.physical_key = physical_key;
|
||||
event.logical_key = key.clone();
|
||||
event.location = code_to_location(physical_key);
|
||||
let location_mask = ModLocationMask::from_location(event.location);
|
||||
|
||||
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
|
||||
let phys_mod = phys_mod_state
|
||||
.entry(key)
|
||||
.or_insert(ModLocationMask::empty());
|
||||
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
|
||||
let phys_mod = phys_mod_state
|
||||
.entry(key)
|
||||
.or_insert(ModLocationMask::empty());
|
||||
|
||||
let is_active = current_modifiers.state().contains(event_modifier);
|
||||
let mut events = VecDeque::with_capacity(2);
|
||||
let is_active = current_modifiers.state().contains(event_modifier);
|
||||
let mut events = VecDeque::with_capacity(2);
|
||||
|
||||
// There is no API for getting whether the button was pressed or released
|
||||
// during this event. For this reason we have to do a bit of magic below
|
||||
// to come up with a good guess whether this key was pressed or released.
|
||||
// (This is not trivial because there are multiple buttons that may affect
|
||||
// the same modifier)
|
||||
if !is_active {
|
||||
event.state = Released;
|
||||
if phys_mod.contains(ModLocationMask::LEFT) {
|
||||
let mut event = event.clone();
|
||||
event.location = KeyLocation::Left;
|
||||
event.physical_key = get_left_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
if phys_mod.contains(ModLocationMask::RIGHT) {
|
||||
event.location = KeyLocation::Right;
|
||||
event.physical_key = get_right_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
*phys_mod = ModLocationMask::empty();
|
||||
} else {
|
||||
// is_active
|
||||
if *phys_mod == location_mask {
|
||||
// Here we hit a contradiction:
|
||||
// The modifier state was "changed" to active,
|
||||
// yet the only pressed modifier key was the one that we
|
||||
// just got a change event for.
|
||||
// This seemingly means that the only pressed modifier is now released,
|
||||
// but at the same time the modifier became active.
|
||||
//
|
||||
// But this scenario is possible if we released modifiers
|
||||
// while the application was not in focus. (Because we don't
|
||||
// get informed of modifier key events while the application
|
||||
// is not focused)
|
||||
|
||||
// In this case we prioritize the information
|
||||
// about the current modifier state which means
|
||||
// that the button was pressed.
|
||||
event.state = Pressed;
|
||||
// There is no API for getting whether the button was pressed or released
|
||||
// during this event. For this reason we have to do a bit of magic below
|
||||
// to come up with a good guess whether this key was pressed or released.
|
||||
// (This is not trivial because there are multiple buttons that may affect
|
||||
// the same modifier)
|
||||
if !is_active {
|
||||
event.state = Released;
|
||||
if phys_mod.contains(ModLocationMask::LEFT) {
|
||||
let mut event = event.clone();
|
||||
event.location = KeyLocation::Left;
|
||||
event.physical_key = get_left_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
if phys_mod.contains(ModLocationMask::RIGHT) {
|
||||
event.location = KeyLocation::Right;
|
||||
event.physical_key = get_right_modifier_code(&event.logical_key).into();
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
*phys_mod = ModLocationMask::empty();
|
||||
} else {
|
||||
phys_mod.toggle(location_mask);
|
||||
let is_pressed = phys_mod.contains(location_mask);
|
||||
event.state = if is_pressed { Pressed } else { Released };
|
||||
if *phys_mod == location_mask {
|
||||
// Here we hit a contradiction:
|
||||
// The modifier state was "changed" to active,
|
||||
// yet the only pressed modifier key was the one that we
|
||||
// just got a change event for.
|
||||
// This seemingly means that the only pressed modifier is now released,
|
||||
// but at the same time the modifier became active.
|
||||
//
|
||||
// But this scenario is possible if we released modifiers
|
||||
// while the application was not in focus. (Because we don't
|
||||
// get informed of modifier key events while the application
|
||||
// is not focused)
|
||||
|
||||
// In this case we prioritize the information
|
||||
// about the current modifier state which means
|
||||
// that the button was pressed.
|
||||
event.state = Pressed;
|
||||
} else {
|
||||
phys_mod.toggle(location_mask);
|
||||
let is_pressed = phys_mod.contains(location_mask);
|
||||
event.state = if is_pressed { Pressed } else { Released };
|
||||
}
|
||||
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
|
||||
events.push_back(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
drop(phys_mod_state);
|
||||
|
||||
drop(phys_mod_state);
|
||||
|
||||
for event in events {
|
||||
self.queue_event(event);
|
||||
for event in events {
|
||||
self.queue_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ use super::appkit::{
|
||||
NSView, NSWindow, NSWindowButton, NSWindowLevel, NSWindowSharingType, NSWindowStyleMask,
|
||||
NSWindowTabbingMode, NSWindowTitleVisibility,
|
||||
};
|
||||
use super::ffi::CGSMainConnectionID;
|
||||
use super::ffi::CGSSetWindowBackgroundBlurRadius;
|
||||
|
||||
pub(crate) struct Window {
|
||||
window: MainThreadBound<Id<WinitWindow>>,
|
||||
@@ -494,6 +496,10 @@ impl WinitWindow {
|
||||
this.setBackgroundColor(&NSColor::clear());
|
||||
}
|
||||
|
||||
if attrs.blur {
|
||||
this.set_blur(attrs.blur);
|
||||
}
|
||||
|
||||
if let Some(dim) = attrs.min_inner_size {
|
||||
this.set_min_inner_size(Some(dim));
|
||||
}
|
||||
@@ -582,7 +588,15 @@ impl WinitWindow {
|
||||
self.setOpaque(!transparent)
|
||||
}
|
||||
|
||||
pub fn set_blur(&self, _blur: bool) {}
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
// NOTE: in general we want to specify the blur radius, but the choice of 80
|
||||
// should be a reasonable default.
|
||||
let radius = if blur { 80 } else { 0 };
|
||||
let window_number = self.windowNumber();
|
||||
unsafe {
|
||||
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
match visible {
|
||||
|
||||
@@ -8,17 +8,18 @@ use std::{
|
||||
};
|
||||
|
||||
use orbclient::{
|
||||
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MoveEvent, QuitEvent,
|
||||
ResizeEvent, ScrollEvent, TextInputEvent,
|
||||
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent,
|
||||
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
|
||||
};
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::{
|
||||
error::EventLoopError,
|
||||
event::{self, Ime, Modifiers, StartCause},
|
||||
event_loop::{self, ControlFlow, DeviceEvents},
|
||||
keyboard::{
|
||||
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
|
||||
PhysicalKey,
|
||||
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey,
|
||||
NativeKeyCode, PhysicalKey,
|
||||
},
|
||||
window::WindowId as RootWindowId,
|
||||
};
|
||||
@@ -28,90 +29,107 @@ use super::{
|
||||
RedoxSocket, TimeSocket, WindowId, WindowProperties,
|
||||
};
|
||||
|
||||
fn convert_scancode(scancode: u8) -> PhysicalKey {
|
||||
PhysicalKey::Code(match scancode {
|
||||
orbclient::K_A => KeyCode::KeyA,
|
||||
orbclient::K_B => KeyCode::KeyB,
|
||||
orbclient::K_C => KeyCode::KeyC,
|
||||
orbclient::K_D => KeyCode::KeyD,
|
||||
orbclient::K_E => KeyCode::KeyE,
|
||||
orbclient::K_F => KeyCode::KeyF,
|
||||
orbclient::K_G => KeyCode::KeyG,
|
||||
orbclient::K_H => KeyCode::KeyH,
|
||||
orbclient::K_I => KeyCode::KeyI,
|
||||
orbclient::K_J => KeyCode::KeyJ,
|
||||
orbclient::K_K => KeyCode::KeyK,
|
||||
orbclient::K_L => KeyCode::KeyL,
|
||||
orbclient::K_M => KeyCode::KeyM,
|
||||
orbclient::K_N => KeyCode::KeyN,
|
||||
orbclient::K_O => KeyCode::KeyO,
|
||||
orbclient::K_P => KeyCode::KeyP,
|
||||
orbclient::K_Q => KeyCode::KeyQ,
|
||||
orbclient::K_R => KeyCode::KeyR,
|
||||
orbclient::K_S => KeyCode::KeyS,
|
||||
orbclient::K_T => KeyCode::KeyT,
|
||||
orbclient::K_U => KeyCode::KeyU,
|
||||
orbclient::K_V => KeyCode::KeyV,
|
||||
orbclient::K_W => KeyCode::KeyW,
|
||||
orbclient::K_X => KeyCode::KeyX,
|
||||
orbclient::K_Y => KeyCode::KeyY,
|
||||
orbclient::K_Z => KeyCode::KeyZ,
|
||||
orbclient::K_0 => KeyCode::Digit0,
|
||||
orbclient::K_1 => KeyCode::Digit1,
|
||||
orbclient::K_2 => KeyCode::Digit2,
|
||||
orbclient::K_3 => KeyCode::Digit3,
|
||||
orbclient::K_4 => KeyCode::Digit4,
|
||||
orbclient::K_5 => KeyCode::Digit5,
|
||||
orbclient::K_6 => KeyCode::Digit6,
|
||||
orbclient::K_7 => KeyCode::Digit7,
|
||||
orbclient::K_8 => KeyCode::Digit8,
|
||||
orbclient::K_9 => KeyCode::Digit9,
|
||||
fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
|
||||
// Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html
|
||||
let (key_code, named_key_opt) = match scancode {
|
||||
orbclient::K_A => (KeyCode::KeyA, None),
|
||||
orbclient::K_B => (KeyCode::KeyB, None),
|
||||
orbclient::K_C => (KeyCode::KeyC, None),
|
||||
orbclient::K_D => (KeyCode::KeyD, None),
|
||||
orbclient::K_E => (KeyCode::KeyE, None),
|
||||
orbclient::K_F => (KeyCode::KeyF, None),
|
||||
orbclient::K_G => (KeyCode::KeyG, None),
|
||||
orbclient::K_H => (KeyCode::KeyH, None),
|
||||
orbclient::K_I => (KeyCode::KeyI, None),
|
||||
orbclient::K_J => (KeyCode::KeyJ, None),
|
||||
orbclient::K_K => (KeyCode::KeyK, None),
|
||||
orbclient::K_L => (KeyCode::KeyL, None),
|
||||
orbclient::K_M => (KeyCode::KeyM, None),
|
||||
orbclient::K_N => (KeyCode::KeyN, None),
|
||||
orbclient::K_O => (KeyCode::KeyO, None),
|
||||
orbclient::K_P => (KeyCode::KeyP, None),
|
||||
orbclient::K_Q => (KeyCode::KeyQ, None),
|
||||
orbclient::K_R => (KeyCode::KeyR, None),
|
||||
orbclient::K_S => (KeyCode::KeyS, None),
|
||||
orbclient::K_T => (KeyCode::KeyT, None),
|
||||
orbclient::K_U => (KeyCode::KeyU, None),
|
||||
orbclient::K_V => (KeyCode::KeyV, None),
|
||||
orbclient::K_W => (KeyCode::KeyW, None),
|
||||
orbclient::K_X => (KeyCode::KeyX, None),
|
||||
orbclient::K_Y => (KeyCode::KeyY, None),
|
||||
orbclient::K_Z => (KeyCode::KeyZ, None),
|
||||
orbclient::K_0 => (KeyCode::Digit0, None),
|
||||
orbclient::K_1 => (KeyCode::Digit1, None),
|
||||
orbclient::K_2 => (KeyCode::Digit2, None),
|
||||
orbclient::K_3 => (KeyCode::Digit3, None),
|
||||
orbclient::K_4 => (KeyCode::Digit4, None),
|
||||
orbclient::K_5 => (KeyCode::Digit5, None),
|
||||
orbclient::K_6 => (KeyCode::Digit6, None),
|
||||
orbclient::K_7 => (KeyCode::Digit7, None),
|
||||
orbclient::K_8 => (KeyCode::Digit8, None),
|
||||
orbclient::K_9 => (KeyCode::Digit9, None),
|
||||
|
||||
orbclient::K_TICK => KeyCode::Backquote,
|
||||
orbclient::K_MINUS => KeyCode::Minus,
|
||||
orbclient::K_EQUALS => KeyCode::Equal,
|
||||
orbclient::K_BACKSLASH => KeyCode::Backslash,
|
||||
orbclient::K_BRACE_OPEN => KeyCode::BracketLeft,
|
||||
orbclient::K_BRACE_CLOSE => KeyCode::BracketRight,
|
||||
orbclient::K_SEMICOLON => KeyCode::Semicolon,
|
||||
orbclient::K_QUOTE => KeyCode::Quote,
|
||||
orbclient::K_COMMA => KeyCode::Comma,
|
||||
orbclient::K_PERIOD => KeyCode::Period,
|
||||
orbclient::K_SLASH => KeyCode::Slash,
|
||||
orbclient::K_BKSP => KeyCode::Backspace,
|
||||
orbclient::K_SPACE => KeyCode::Space,
|
||||
orbclient::K_TAB => KeyCode::Tab,
|
||||
//orbclient::K_CAPS => KeyCode::CAPS,
|
||||
orbclient::K_LEFT_SHIFT => KeyCode::ShiftLeft,
|
||||
orbclient::K_RIGHT_SHIFT => KeyCode::ShiftRight,
|
||||
orbclient::K_CTRL => KeyCode::ControlLeft,
|
||||
orbclient::K_ALT => KeyCode::AltLeft,
|
||||
orbclient::K_ENTER => KeyCode::Enter,
|
||||
orbclient::K_ESC => KeyCode::Escape,
|
||||
orbclient::K_F1 => KeyCode::F1,
|
||||
orbclient::K_F2 => KeyCode::F2,
|
||||
orbclient::K_F3 => KeyCode::F3,
|
||||
orbclient::K_F4 => KeyCode::F4,
|
||||
orbclient::K_F5 => KeyCode::F5,
|
||||
orbclient::K_F6 => KeyCode::F6,
|
||||
orbclient::K_F7 => KeyCode::F7,
|
||||
orbclient::K_F8 => KeyCode::F8,
|
||||
orbclient::K_F9 => KeyCode::F9,
|
||||
orbclient::K_F10 => KeyCode::F10,
|
||||
orbclient::K_HOME => KeyCode::Home,
|
||||
orbclient::K_UP => KeyCode::ArrowUp,
|
||||
orbclient::K_PGUP => KeyCode::PageUp,
|
||||
orbclient::K_LEFT => KeyCode::ArrowLeft,
|
||||
orbclient::K_RIGHT => KeyCode::ArrowRight,
|
||||
orbclient::K_END => KeyCode::End,
|
||||
orbclient::K_DOWN => KeyCode::ArrowDown,
|
||||
orbclient::K_PGDN => KeyCode::PageDown,
|
||||
orbclient::K_DEL => KeyCode::Delete,
|
||||
orbclient::K_F11 => KeyCode::F11,
|
||||
orbclient::K_F12 => KeyCode::F12,
|
||||
orbclient::K_ALT => (KeyCode::AltLeft, Some(NamedKey::Alt)),
|
||||
orbclient::K_ALT_GR => (KeyCode::AltRight, Some(NamedKey::AltGraph)),
|
||||
orbclient::K_BACKSLASH => (KeyCode::Backslash, None),
|
||||
orbclient::K_BKSP => (KeyCode::Backspace, Some(NamedKey::Backspace)),
|
||||
orbclient::K_BRACE_CLOSE => (KeyCode::BracketRight, None),
|
||||
orbclient::K_BRACE_OPEN => (KeyCode::BracketLeft, None),
|
||||
orbclient::K_CAPS => (KeyCode::CapsLock, Some(NamedKey::CapsLock)),
|
||||
orbclient::K_COMMA => (KeyCode::Comma, None),
|
||||
orbclient::K_CTRL => (KeyCode::ControlLeft, Some(NamedKey::Control)),
|
||||
orbclient::K_DEL => (KeyCode::Delete, Some(NamedKey::Delete)),
|
||||
orbclient::K_DOWN => (KeyCode::ArrowDown, Some(NamedKey::ArrowDown)),
|
||||
orbclient::K_END => (KeyCode::End, Some(NamedKey::End)),
|
||||
orbclient::K_ENTER => (KeyCode::Enter, Some(NamedKey::Enter)),
|
||||
orbclient::K_EQUALS => (KeyCode::Equal, None),
|
||||
orbclient::K_ESC => (KeyCode::Escape, Some(NamedKey::Escape)),
|
||||
orbclient::K_F1 => (KeyCode::F1, Some(NamedKey::F1)),
|
||||
orbclient::K_F2 => (KeyCode::F2, Some(NamedKey::F2)),
|
||||
orbclient::K_F3 => (KeyCode::F3, Some(NamedKey::F3)),
|
||||
orbclient::K_F4 => (KeyCode::F4, Some(NamedKey::F4)),
|
||||
orbclient::K_F5 => (KeyCode::F5, Some(NamedKey::F5)),
|
||||
orbclient::K_F6 => (KeyCode::F6, Some(NamedKey::F6)),
|
||||
orbclient::K_F7 => (KeyCode::F7, Some(NamedKey::F7)),
|
||||
orbclient::K_F8 => (KeyCode::F8, Some(NamedKey::F8)),
|
||||
orbclient::K_F9 => (KeyCode::F9, Some(NamedKey::F9)),
|
||||
orbclient::K_F10 => (KeyCode::F10, Some(NamedKey::F10)),
|
||||
orbclient::K_F11 => (KeyCode::F11, Some(NamedKey::F11)),
|
||||
orbclient::K_F12 => (KeyCode::F12, Some(NamedKey::F12)),
|
||||
orbclient::K_HOME => (KeyCode::Home, Some(NamedKey::Home)),
|
||||
orbclient::K_LEFT => (KeyCode::ArrowLeft, Some(NamedKey::ArrowLeft)),
|
||||
orbclient::K_LEFT_SHIFT => (KeyCode::ShiftLeft, Some(NamedKey::Shift)),
|
||||
orbclient::K_MINUS => (KeyCode::Minus, None),
|
||||
orbclient::K_NUM_0 => (KeyCode::Numpad0, None),
|
||||
orbclient::K_NUM_1 => (KeyCode::Numpad1, None),
|
||||
orbclient::K_NUM_2 => (KeyCode::Numpad2, None),
|
||||
orbclient::K_NUM_3 => (KeyCode::Numpad3, None),
|
||||
orbclient::K_NUM_4 => (KeyCode::Numpad4, None),
|
||||
orbclient::K_NUM_5 => (KeyCode::Numpad5, None),
|
||||
orbclient::K_NUM_6 => (KeyCode::Numpad6, None),
|
||||
orbclient::K_NUM_7 => (KeyCode::Numpad7, None),
|
||||
orbclient::K_NUM_8 => (KeyCode::Numpad8, None),
|
||||
orbclient::K_NUM_9 => (KeyCode::Numpad9, None),
|
||||
orbclient::K_PERIOD => (KeyCode::Period, None),
|
||||
orbclient::K_PGDN => (KeyCode::PageDown, Some(NamedKey::PageDown)),
|
||||
orbclient::K_PGUP => (KeyCode::PageUp, Some(NamedKey::PageUp)),
|
||||
orbclient::K_QUOTE => (KeyCode::Quote, None),
|
||||
orbclient::K_RIGHT => (KeyCode::ArrowRight, Some(NamedKey::ArrowRight)),
|
||||
orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)),
|
||||
orbclient::K_SEMICOLON => (KeyCode::Semicolon, None),
|
||||
orbclient::K_SLASH => (KeyCode::Slash, None),
|
||||
orbclient::K_SPACE => (KeyCode::Space, Some(NamedKey::Space)),
|
||||
orbclient::K_SUPER => (KeyCode::SuperLeft, Some(NamedKey::Super)),
|
||||
orbclient::K_TAB => (KeyCode::Tab, Some(NamedKey::Tab)),
|
||||
orbclient::K_TICK => (KeyCode::Backquote, None),
|
||||
orbclient::K_UP => (KeyCode::ArrowUp, Some(NamedKey::ArrowUp)),
|
||||
orbclient::K_VOLUME_DOWN => (KeyCode::AudioVolumeDown, Some(NamedKey::AudioVolumeDown)),
|
||||
orbclient::K_VOLUME_TOGGLE => (KeyCode::AudioVolumeMute, Some(NamedKey::AudioVolumeMute)),
|
||||
orbclient::K_VOLUME_UP => (KeyCode::AudioVolumeUp, Some(NamedKey::AudioVolumeUp)),
|
||||
|
||||
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
|
||||
})
|
||||
_ => return (PhysicalKey::Unidentified(NativeKeyCode::Unidentified), None),
|
||||
};
|
||||
(PhysicalKey::Code(key_code), named_key_opt)
|
||||
}
|
||||
|
||||
fn element_state(pressed: bool) -> event::ElementState {
|
||||
@@ -153,6 +171,22 @@ struct EventState {
|
||||
}
|
||||
|
||||
impl EventState {
|
||||
fn character_all_modifiers(&self, character: char) -> char {
|
||||
// Modify character if Ctrl is pressed
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if self.keyboard.contains(KeyboardModifierState::LCTRL)
|
||||
|| self.keyboard.contains(KeyboardModifierState::RCTRL)
|
||||
{
|
||||
if character.is_ascii_lowercase() {
|
||||
return ((character as u8 - b'a') + 1) as char;
|
||||
}
|
||||
//TODO: more control key variants?
|
||||
}
|
||||
|
||||
// Return character as-is if no special handling required
|
||||
character
|
||||
}
|
||||
|
||||
fn key(&mut self, key: PhysicalKey, pressed: bool) {
|
||||
let code = match key {
|
||||
PhysicalKey::Code(code) => code,
|
||||
@@ -333,39 +367,75 @@ impl<T: 'static> EventLoop<T> {
|
||||
{
|
||||
match event_option {
|
||||
EventOption::Key(KeyEvent {
|
||||
character: _,
|
||||
character,
|
||||
scancode,
|
||||
pressed,
|
||||
}) => {
|
||||
if scancode != 0 {
|
||||
let physical_key = convert_scancode(scancode);
|
||||
let modifiers_before = event_state.keyboard;
|
||||
event_state.key(physical_key, pressed);
|
||||
// Convert scancode
|
||||
let (physical_key, named_key_opt) = convert_scancode(scancode);
|
||||
|
||||
// Get previous modifiers and update modifiers based on physical key
|
||||
let modifiers_before = event_state.keyboard;
|
||||
event_state.key(physical_key, pressed);
|
||||
|
||||
// Default to unidentified key with no text
|
||||
let mut logical_key = Key::Unidentified(NativeKey::Unidentified);
|
||||
let mut key_without_modifiers = logical_key.clone();
|
||||
let mut text = None;
|
||||
let mut text_with_all_modifiers = None;
|
||||
|
||||
// Set key and text based on character
|
||||
if character != '\0' {
|
||||
let mut tmp = [0u8; 4];
|
||||
let character_str = character.encode_utf8(&mut tmp);
|
||||
// The key with Shift and Caps Lock applied (but not Ctrl)
|
||||
logical_key = Key::Character(character_str.into());
|
||||
// The key without Shift or Caps Lock applied
|
||||
key_without_modifiers =
|
||||
Key::Character(SmolStr::from_iter(character.to_lowercase()));
|
||||
if pressed {
|
||||
// The key with Shift and Caps Lock applied (but not Ctrl)
|
||||
text = Some(character_str.into());
|
||||
// The key with Shift, Caps Lock, and Ctrl applied
|
||||
let character_all_modifiers =
|
||||
event_state.character_all_modifiers(character);
|
||||
text_with_all_modifiers =
|
||||
Some(character_all_modifiers.encode_utf8(&mut tmp).into())
|
||||
}
|
||||
};
|
||||
|
||||
// Override key if a named key was found (this is to allow Enter to replace '\n')
|
||||
if let Some(named_key) = named_key_opt {
|
||||
logical_key = Key::Named(named_key);
|
||||
key_without_modifiers = logical_key.clone();
|
||||
}
|
||||
|
||||
event_handler(event::Event::WindowEvent {
|
||||
window_id: RootWindowId(window_id),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id: event::DeviceId(DeviceId),
|
||||
event: event::KeyEvent {
|
||||
logical_key,
|
||||
physical_key,
|
||||
location: KeyLocation::Standard,
|
||||
state: element_state(pressed),
|
||||
repeat: false,
|
||||
text,
|
||||
platform_specific: KeyEventExtra {
|
||||
key_without_modifiers,
|
||||
text_with_all_modifiers,
|
||||
},
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
});
|
||||
|
||||
// If the state of the modifiers has changed, send the event.
|
||||
if modifiers_before != event_state.keyboard {
|
||||
event_handler(event::Event::WindowEvent {
|
||||
window_id: RootWindowId(window_id),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id: event::DeviceId(DeviceId),
|
||||
event: event::KeyEvent {
|
||||
logical_key: Key::Unidentified(NativeKey::Unidentified),
|
||||
physical_key,
|
||||
location: KeyLocation::Standard,
|
||||
state: element_state(pressed),
|
||||
repeat: false,
|
||||
text: None,
|
||||
|
||||
platform_specific: KeyEventExtra {},
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
});
|
||||
|
||||
// If the state of the modifiers has changed, send the event.
|
||||
if modifiers_before != event_state.keyboard {
|
||||
event_handler(event::Event::WindowEvent {
|
||||
window_id: RootWindowId(window_id),
|
||||
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
|
||||
})
|
||||
}
|
||||
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
|
||||
})
|
||||
}
|
||||
}
|
||||
EventOption::TextInput(TextInputEvent { character }) => {
|
||||
@@ -387,6 +457,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
},
|
||||
});
|
||||
}
|
||||
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
|
||||
event_handler(event::Event::DeviceEvent {
|
||||
device_id: event::DeviceId(DeviceId),
|
||||
event: event::DeviceEvent::MouseMotion {
|
||||
delta: (dx as f64, dy as f64),
|
||||
},
|
||||
});
|
||||
}
|
||||
EventOption::Button(ButtonEvent {
|
||||
left,
|
||||
middle,
|
||||
@@ -440,7 +518,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Acknowledge resize after event loop.
|
||||
event_state.resize_opt = Some((width, height));
|
||||
}
|
||||
//TODO: Clipboard
|
||||
//TODO: Screen, Clipboard, Drop
|
||||
EventOption::Hover(HoverEvent { entered }) => {
|
||||
if entered {
|
||||
event_handler(event::Event::WindowEvent {
|
||||
|
||||
@@ -4,7 +4,12 @@ use std::fmt::{self, Display, Formatter};
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
keyboard::Key,
|
||||
};
|
||||
|
||||
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
mod event_loop;
|
||||
@@ -259,5 +264,8 @@ impl VideoMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventExtra {}
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub key_without_modifiers: Key,
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
|
||||
TimeSocket, WindowId, WindowProperties,
|
||||
EventLoopWindowTarget, MonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
|
||||
RedoxSocket, TimeSocket, WindowId, WindowProperties,
|
||||
};
|
||||
|
||||
// These values match the values uses in the `window_new` function in orbital:
|
||||
@@ -21,7 +21,9 @@ use super::{
|
||||
const ORBITAL_FLAG_ASYNC: char = 'a';
|
||||
const ORBITAL_FLAG_BACK: char = 'b';
|
||||
const ORBITAL_FLAG_FRONT: char = 'f';
|
||||
const ORBITAL_FLAG_HIDDEN: char = 'h';
|
||||
const ORBITAL_FLAG_BORDERLESS: char = 'l';
|
||||
const ORBITAL_FLAG_MAXIMIZED: char = 'm';
|
||||
const ORBITAL_FLAG_RESIZABLE: char = 'r';
|
||||
const ORBITAL_FLAG_TRANSPARENT: char = 't';
|
||||
|
||||
@@ -58,11 +60,15 @@ impl Window {
|
||||
// Async by default.
|
||||
let mut flag_str = ORBITAL_FLAG_ASYNC.to_string();
|
||||
|
||||
if attrs.maximized {
|
||||
flag_str.push(ORBITAL_FLAG_MAXIMIZED);
|
||||
}
|
||||
|
||||
if attrs.resizable {
|
||||
flag_str.push(ORBITAL_FLAG_RESIZABLE);
|
||||
}
|
||||
|
||||
//TODO: maximized, fullscreen, visible
|
||||
//TODO: fullscreen
|
||||
|
||||
if attrs.transparent {
|
||||
flag_str.push(ORBITAL_FLAG_TRANSPARENT);
|
||||
@@ -72,6 +78,10 @@ impl Window {
|
||||
flag_str.push(ORBITAL_FLAG_BORDERLESS);
|
||||
}
|
||||
|
||||
if !attrs.visible {
|
||||
flag_str.push(ORBITAL_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
match attrs.window_level {
|
||||
window::WindowLevel::AlwaysOnBottom => {
|
||||
flag_str.push(ORBITAL_FLAG_BACK);
|
||||
@@ -130,6 +140,23 @@ impl Window {
|
||||
f(self)
|
||||
}
|
||||
|
||||
fn get_flag(&self, flag: char) -> Result<bool, error::ExternalError> {
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
let path = self
|
||||
.window_socket
|
||||
.fpath(&mut buf)
|
||||
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
|
||||
let properties = WindowProperties::new(path);
|
||||
Ok(properties.flags.contains(flag))
|
||||
}
|
||||
|
||||
fn set_flag(&self, flag: char, value: bool) -> Result<(), error::ExternalError> {
|
||||
self.window_socket
|
||||
.write(format!("F,{flag},{}", if value { 1 } else { 0 }).as_bytes())
|
||||
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId {
|
||||
@@ -255,17 +282,21 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_transparent(&self, _transparent: bool) {}
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_TRANSPARENT, transparent);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_blur(&self, _blur: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, _visibility: bool) {}
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_HIDDEN, !visible);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
None
|
||||
Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -277,17 +308,13 @@ impl Window {
|
||||
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, _resizeable: bool) {}
|
||||
pub fn set_resizable(&self, resizeable: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_RESIZABLE, resizeable);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
let path = self
|
||||
.window_socket
|
||||
.fpath(&mut buf)
|
||||
.expect("failed to read properties");
|
||||
let properties = WindowProperties::new(path);
|
||||
properties.flags.contains(ORBITAL_FLAG_RESIZABLE)
|
||||
self.get_flag(ORBITAL_FLAG_RESIZABLE).unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -299,11 +326,13 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {}
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_MAXIMIZED, maximized);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
false
|
||||
self.get_flag(ORBITAL_FLAG_MAXIMIZED).unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -315,21 +344,30 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, _decorations: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
let path = self
|
||||
.window_socket
|
||||
.fpath(&mut buf)
|
||||
.expect("failed to read properties");
|
||||
let properties = WindowProperties::new(path);
|
||||
!properties.flags.contains(ORBITAL_FLAG_BORDERLESS)
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_BORDERLESS, !decorations);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, _level: window::WindowLevel) {}
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
!self.get_flag(ORBITAL_FLAG_BORDERLESS).unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, level: window::WindowLevel) {
|
||||
match level {
|
||||
window::WindowLevel::AlwaysOnBottom => {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_BACK, true);
|
||||
}
|
||||
window::WindowLevel::Normal => {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_BACK, false);
|
||||
let _ = self.set_flag(ORBITAL_FLAG_FRONT, false);
|
||||
}
|
||||
window::WindowLevel::AlwaysOnTop => {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_FRONT, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
|
||||
@@ -360,30 +398,58 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, _: window::CursorGrabMode) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
pub fn set_cursor_grab(
|
||||
&self,
|
||||
mode: window::CursorGrabMode,
|
||||
) -> Result<(), error::ExternalError> {
|
||||
let (grab, relative) = match mode {
|
||||
window::CursorGrabMode::None => (false, false),
|
||||
window::CursorGrabMode::Confined => (true, false),
|
||||
window::CursorGrabMode::Locked => (true, true),
|
||||
};
|
||||
self.window_socket
|
||||
.write(format!("M,G,{}", if grab { 1 } else { 0 }).as_bytes())
|
||||
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
|
||||
self.window_socket
|
||||
.write(format!("M,R,{}", if relative { 1 } else { 0 }).as_bytes())
|
||||
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, _: bool) {}
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let _ = self
|
||||
.window_socket
|
||||
.write(format!("M,C,{}", if visible { 1 } else { 0 }).as_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
self.window_socket
|
||||
.write(b"D")
|
||||
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_resize_window(
|
||||
&self,
|
||||
_direction: window::ResizeDirection,
|
||||
direction: window::ResizeDirection,
|
||||
) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
let arg = match direction {
|
||||
window::ResizeDirection::East => "R",
|
||||
window::ResizeDirection::North => "T",
|
||||
window::ResizeDirection::NorthEast => "T,R",
|
||||
window::ResizeDirection::NorthWest => "T,L",
|
||||
window::ResizeDirection::South => "B",
|
||||
window::ResizeDirection::SouthEast => "B,R",
|
||||
window::ResizeDirection::SouthWest => "B,L",
|
||||
window::ResizeDirection::West => "L",
|
||||
};
|
||||
self.window_socket
|
||||
.write(format!("D,{}", arg).as_bytes())
|
||||
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::window::WindowId;
|
||||
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
clone::Clone,
|
||||
collections::{HashSet, VecDeque},
|
||||
iter,
|
||||
ops::Deref,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::clone::Clone;
|
||||
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
||||
use std::iter;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use web_sys::Element;
|
||||
|
||||
use super::runner::{EventWrapper, Execution};
|
||||
use super::{
|
||||
super::{monitor::MonitorHandle, KeyEventExtra},
|
||||
@@ -122,6 +123,25 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
}
|
||||
});
|
||||
|
||||
// It is possible that at this point the canvas has
|
||||
// been focused before the callback can be called.
|
||||
let focused = canvas
|
||||
.document()
|
||||
.active_element()
|
||||
.filter(|element| {
|
||||
let canvas: &Element = canvas.raw();
|
||||
element == canvas
|
||||
})
|
||||
.is_some();
|
||||
|
||||
if focused {
|
||||
canvas.has_focus.set(true);
|
||||
self.runner.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(id),
|
||||
event: WindowEvent::Focused(true),
|
||||
})
|
||||
}
|
||||
|
||||
let runner = self.runner.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
canvas.on_keyboard_press(
|
||||
@@ -649,6 +669,8 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));
|
||||
|
||||
canvas.on_touch_end();
|
||||
|
||||
canvas.on_context_menu(prevent_default);
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
|
||||
// compliant way.
|
||||
|
||||
// TODO: FP, remove when <https://github.com/rust-lang/rust/issues/121621> is fixed.
|
||||
#![allow(unknown_lints, non_local_definitions)]
|
||||
|
||||
mod r#async;
|
||||
mod device;
|
||||
mod error;
|
||||
|
||||
@@ -5,7 +5,8 @@ use std::sync::{Arc, Mutex};
|
||||
use smol_str::SmolStr;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent,
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
|
||||
PointerEvent, WheelEvent,
|
||||
};
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
@@ -42,6 +43,7 @@ pub struct Canvas {
|
||||
on_intersect: Option<IntersectionObserverHandle>,
|
||||
animation_frame_handler: AnimationFrameHandler,
|
||||
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||
on_context_menu: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||
}
|
||||
|
||||
pub struct Common {
|
||||
@@ -152,6 +154,7 @@ impl Canvas {
|
||||
on_intersect: None,
|
||||
animation_frame_handler: AnimationFrameHandler::new(window),
|
||||
on_touch_end: None,
|
||||
on_context_menu: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -446,6 +449,17 @@ impl Canvas {
|
||||
self.on_touch_end = Some(self.common.add_transient_event("touchend", |_| {}));
|
||||
}
|
||||
|
||||
pub(crate) fn on_context_menu(&mut self, prevent_default: bool) {
|
||||
self.on_context_menu = Some(self.common.add_event(
|
||||
"contextmenu",
|
||||
move |event: PointerEvent| {
|
||||
if prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
self.common.fullscreen_handler.request_fullscreen()
|
||||
}
|
||||
@@ -524,6 +538,7 @@ impl Canvas {
|
||||
self.animation_frame_handler.cancel();
|
||||
self.on_touch_end = None;
|
||||
self.common.fullscreen_handler.cancel();
|
||||
self.on_context_menu = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use smol_str::SmolStr;
|
||||
use std::convert::TryInto;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
|
||||
@@ -81,9 +80,22 @@ impl MouseButton {
|
||||
}
|
||||
|
||||
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type MouseEventExt;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = offsetX)]
|
||||
fn offset_x(this: &MouseEventExt) -> f64;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = offsetY)]
|
||||
fn offset_y(this: &MouseEventExt) -> f64;
|
||||
}
|
||||
|
||||
let event: &MouseEventExt = event.unchecked_ref();
|
||||
|
||||
LogicalPosition {
|
||||
x: event.offset_x() as f64,
|
||||
y: event.offset_y() as f64,
|
||||
x: event.offset_x(),
|
||||
y: event.offset_y(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +256,7 @@ pub fn pointer_move_event(event: PointerEvent) -> impl Iterator<Item = PointerEv
|
||||
// See <https://github.com/rust-windowing/winit/issues/2875>.
|
||||
pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
|
||||
thread_local! {
|
||||
static POINTER_RAW_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
static POINTER_RAW_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
||||
}
|
||||
|
||||
POINTER_RAW_SUPPORT.with(|support| {
|
||||
@@ -267,7 +279,7 @@ pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
|
||||
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
|
||||
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {
|
||||
thread_local! {
|
||||
static COALESCED_EVENTS_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
static COALESCED_EVENTS_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
||||
}
|
||||
|
||||
COALESCED_EVENTS_SUPPORT.with(|support| {
|
||||
|
||||
@@ -11,7 +11,7 @@ use web_sys::{Document, Element, HtmlCanvasElement};
|
||||
use super::EventListenerHandle;
|
||||
|
||||
thread_local! {
|
||||
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
||||
}
|
||||
|
||||
pub struct FullscreenHandler {
|
||||
|
||||
@@ -126,7 +126,7 @@ impl ResizeScaleInternal {
|
||||
(-webkit-device-pixel-ratio: {current_scale})",
|
||||
);
|
||||
let mql = MediaQueryListHandle::new(window, &media_query, closure);
|
||||
assert!(
|
||||
debug_assert!(
|
||||
mql.mql().matches(),
|
||||
"created media query doesn't match, {current_scale} != {}",
|
||||
super::scale_factor(window)
|
||||
|
||||
@@ -117,9 +117,7 @@ impl Schedule {
|
||||
let channel = MessageChannel::new().unwrap();
|
||||
let closure = Closure::new(f);
|
||||
let port_1 = channel.port1();
|
||||
port_1
|
||||
.add_event_listener_with_callback("message", closure.as_ref().unchecked_ref())
|
||||
.expect("Failed to set message handler");
|
||||
port_1.set_onmessage(Some(closure.as_ref().unchecked_ref()));
|
||||
port_1.start();
|
||||
|
||||
let port_2 = channel.port2();
|
||||
@@ -178,6 +176,7 @@ impl Drop for Schedule {
|
||||
} => {
|
||||
window.clear_timeout_with_handle(*handle);
|
||||
port.close();
|
||||
port.set_onmessage(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,7 +198,7 @@ fn duration_millis_ceil(duration: Duration) -> u32 {
|
||||
|
||||
fn has_scheduler_support(window: &web_sys::Window) -> bool {
|
||||
thread_local! {
|
||||
static SCHEDULER_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
static SCHEDULER_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
||||
}
|
||||
|
||||
SCHEDULER_SUPPORT.with(|support| {
|
||||
@@ -221,7 +220,7 @@ fn has_scheduler_support(window: &web_sys::Window) -> bool {
|
||||
|
||||
fn has_idle_callback_support(window: &web_sys::Window) -> bool {
|
||||
thread_local! {
|
||||
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
||||
}
|
||||
|
||||
IDLE_CALLBACK_SUPPORT.with(|support| {
|
||||
|
||||
@@ -9,8 +9,8 @@ use windows_sys::Win32::{
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{
|
||||
DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, MDT_EFFECTIVE_DPI,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
||||
MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
},
|
||||
WindowsAndMessaging::IsProcessDPIAware,
|
||||
},
|
||||
@@ -21,8 +21,6 @@ use crate::platform_impl::platform::util::{
|
||||
SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT,
|
||||
};
|
||||
|
||||
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4;
|
||||
|
||||
pub fn become_dpi_aware() {
|
||||
static ENABLE_DPI_AWARENESS: Once = Once::new();
|
||||
ENABLE_DPI_AWARENESS.call_once(|| {
|
||||
|
||||
@@ -21,7 +21,7 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE,
|
||||
Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Graphics::Gdi::{
|
||||
GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient,
|
||||
ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE,
|
||||
@@ -35,13 +35,9 @@ use windows_sys::Win32::{
|
||||
Input::{
|
||||
Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW},
|
||||
KeyboardAndMouse::{
|
||||
MapVirtualKeyW, ReleaseCapture, SetCapture, TrackMouseEvent, MAPVK_VK_TO_VSC_EX,
|
||||
TME_LEAVE, TRACKMOUSEEVENT, VK_NUMLOCK, VK_SHIFT,
|
||||
},
|
||||
Pointer::{
|
||||
POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE, POINTER_INFO,
|
||||
POINTER_PEN_INFO, POINTER_TOUCH_INFO,
|
||||
ReleaseCapture, SetCapture, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT,
|
||||
},
|
||||
Pointer::{POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE},
|
||||
Touch::{
|
||||
CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE,
|
||||
TOUCHEVENTF_UP, TOUCHINPUT,
|
||||
@@ -54,20 +50,19 @@ use windows_sys::Win32::{
|
||||
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos,
|
||||
TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA,
|
||||
HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN,
|
||||
PT_TOUCH, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE,
|
||||
SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER,
|
||||
WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY,
|
||||
WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION,
|
||||
WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT,
|
||||
WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN,
|
||||
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
|
||||
WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
|
||||
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
|
||||
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
|
||||
WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
|
||||
WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED,
|
||||
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
|
||||
WS_VISIBLE,
|
||||
PT_TOUCH, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
|
||||
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
|
||||
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
|
||||
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
|
||||
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE,
|
||||
WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
|
||||
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
|
||||
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
|
||||
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
|
||||
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
|
||||
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP,
|
||||
WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT,
|
||||
WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -80,8 +75,8 @@ use crate::{
|
||||
WindowEvent,
|
||||
},
|
||||
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
keyboard::{KeyCode, ModifiersState, PhysicalKey},
|
||||
platform::{pump_events::PumpStatus, scancode::PhysicalKeyExtScancode},
|
||||
keyboard::ModifiersState,
|
||||
platform::pump_events::PumpStatus,
|
||||
platform_impl::platform::{
|
||||
dark_mode::try_theme,
|
||||
dpi::{become_dpi_aware, dpi_to_scale_factor},
|
||||
@@ -103,37 +98,6 @@ use self::runner::RunnerState;
|
||||
|
||||
use super::window::set_skip_taskbar;
|
||||
|
||||
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointerId: u32,
|
||||
entriesCount: *mut u32,
|
||||
pointerCount: *mut u32,
|
||||
pointerInfo: *mut POINTER_INFO,
|
||||
) -> BOOL;
|
||||
|
||||
type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
|
||||
type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||
device: HANDLE,
|
||||
pointerDeviceRect: *mut RECT,
|
||||
displayRect: *mut RECT,
|
||||
) -> BOOL;
|
||||
|
||||
type GetPointerTouchInfo =
|
||||
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||
|
||||
type GetPointerPenInfo =
|
||||
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
|
||||
|
||||
static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
|
||||
static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
|
||||
static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||
static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||
static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));
|
||||
|
||||
pub(crate) struct WindowData<T: 'static> {
|
||||
pub window_state: Arc<Mutex<WindowState>>,
|
||||
pub event_loop_runner: EventLoopRunnerShared<T>,
|
||||
@@ -356,19 +320,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
/// Wait for one message and dispatch it, optionally with a timeout
|
||||
fn wait_and_dispatch_message(&mut self, timeout: Option<Duration>) {
|
||||
let start = Instant::now();
|
||||
|
||||
let runner = &self.window_target.p.runner_shared;
|
||||
|
||||
let control_flow_timeout = match runner.control_flow() {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Duration::ZERO),
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
Some(wait_deadline.saturating_duration_since(start))
|
||||
}
|
||||
};
|
||||
let timeout = min_timeout(control_flow_timeout, timeout);
|
||||
|
||||
fn get_msg_with_timeout(msg: &mut MSG, timeout: Option<Duration>) -> PumpStatus {
|
||||
unsafe {
|
||||
// A timeout of None means wait indefinitely (so we don't need to call SetTimer)
|
||||
@@ -404,6 +355,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
let runner = &self.window_target.p.runner_shared;
|
||||
|
||||
// We aim to be consistent with the MacOS backend which has a RunLoop
|
||||
// observer that will dispatch AboutToWait when about to wait for
|
||||
// events, and NewEvents after the RunLoop wakes up.
|
||||
@@ -415,6 +368,16 @@ impl<T: 'static> EventLoop<T> {
|
||||
//
|
||||
runner.prepare_wait();
|
||||
|
||||
let control_flow_timeout = match runner.control_flow() {
|
||||
ControlFlow::Wait => None,
|
||||
ControlFlow::Poll => Some(Duration::ZERO),
|
||||
ControlFlow::WaitUntil(wait_deadline) => {
|
||||
let start = Instant::now();
|
||||
Some(wait_deadline.saturating_duration_since(start))
|
||||
}
|
||||
};
|
||||
let timeout = min_timeout(control_flow_timeout, timeout);
|
||||
|
||||
// # Safety
|
||||
// The Windows API has no documented requirement for bitwise
|
||||
// initializing a `MSG` struct (it can be uninitialized memory for the C
|
||||
@@ -567,6 +530,10 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
self.runner_shared.exit_code().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.runner_shared.clear_exit();
|
||||
}
|
||||
|
||||
fn exit_code(&self) -> Option<i32> {
|
||||
self.runner_shared.exit_code()
|
||||
}
|
||||
@@ -729,14 +696,13 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
unsafe {
|
||||
if PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) != false.into() {
|
||||
self.event_send.send(event).ok();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(EventLoopClosed(event))
|
||||
}
|
||||
}
|
||||
self.event_send
|
||||
.send(event)
|
||||
.map(|result| {
|
||||
unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) };
|
||||
result
|
||||
})
|
||||
.map_err(|e| EventLoopClosed(e.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1465,6 +1431,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true))
|
||||
.ok();
|
||||
|
||||
drop(w);
|
||||
userdata.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: CursorEntered {
|
||||
@@ -1487,6 +1454,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false))
|
||||
.ok();
|
||||
|
||||
drop(w);
|
||||
userdata.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: CursorLeft {
|
||||
@@ -1494,12 +1462,13 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
},
|
||||
});
|
||||
}
|
||||
PointerMoveKind::None => (),
|
||||
PointerMoveKind::None => drop(w),
|
||||
}
|
||||
|
||||
// handle spurious WM_MOUSEMOVE messages
|
||||
// see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343
|
||||
// and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html
|
||||
let mut w = userdata.window_state_lock();
|
||||
cursor_moved = w.mouse.last_position != Some(position);
|
||||
w.mouse.last_position = Some(position);
|
||||
}
|
||||
@@ -1829,9 +1798,9 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
Some(SkipPointerFrameMessages),
|
||||
Some(GetPointerDeviceRects),
|
||||
) = (
|
||||
*GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*SKIP_POINTER_FRAME_MESSAGES,
|
||||
*GET_POINTER_DEVICE_RECTS,
|
||||
*util::GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*util::SKIP_POINTER_FRAME_MESSAGES,
|
||||
*util::GET_POINTER_DEVICE_RECTS,
|
||||
) {
|
||||
let pointer_id = super::loword(wparam as u32) as u32;
|
||||
let mut entries_count = 0u32;
|
||||
@@ -1913,7 +1882,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
let force = match pointer_info.pointerType {
|
||||
PT_TOUCH => {
|
||||
let mut touch_info = mem::MaybeUninit::uninit();
|
||||
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
match unsafe {
|
||||
GetPointerTouchInfo(
|
||||
pointer_info.pointerId,
|
||||
@@ -1929,7 +1898,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
}
|
||||
PT_PEN => {
|
||||
let mut pen_info = mem::MaybeUninit::uninit();
|
||||
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
util::GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
match unsafe {
|
||||
GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr())
|
||||
} {
|
||||
@@ -2491,105 +2460,17 @@ unsafe fn handle_raw_input<T: 'static>(userdata: &ThreadMsgTargetData<T>, data:
|
||||
return;
|
||||
}
|
||||
|
||||
let state = if pressed { Pressed } else { Released };
|
||||
let extension = {
|
||||
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
|
||||
0xE000
|
||||
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
|
||||
0xE100
|
||||
} else {
|
||||
0x0000
|
||||
}
|
||||
};
|
||||
let scancode = if keyboard.MakeCode == 0 {
|
||||
// In some cases (often with media keys) the device reports a scancode of 0 but a
|
||||
// valid virtual key. In these cases we obtain the scancode from the virtual key.
|
||||
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
|
||||
} else {
|
||||
keyboard.MakeCode | extension
|
||||
};
|
||||
if scancode == 0xE11D || scancode == 0xE02A {
|
||||
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
|
||||
// Ctrl+NumLock.
|
||||
// This equvalence means that if the user presses Pause, the keyboard will emit two
|
||||
// subsequent keypresses:
|
||||
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
|
||||
// 2, 0x0045 - Which on its own can be interpreted as Pause
|
||||
//
|
||||
// There's another combination which isn't quite an equivalence:
|
||||
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
|
||||
// PrtSc (print screen) produces the following sequence:
|
||||
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
|
||||
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
|
||||
// its own it can be interpreted as PrtSc
|
||||
//
|
||||
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
|
||||
// that there's going to be another event coming, from which we can extract the
|
||||
// appropriate key.
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
return;
|
||||
if let Some(physical_key) = raw_input::get_keyboard_physical_key(keyboard) {
|
||||
let state = if pressed { Pressed } else { Released };
|
||||
|
||||
userdata.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(RawKeyEvent {
|
||||
physical_key,
|
||||
state,
|
||||
}),
|
||||
});
|
||||
}
|
||||
let physical_key = if keyboard.VKey == VK_NUMLOCK {
|
||||
// Historically, the NumLock and the Pause key were one and the same physical key.
|
||||
// The user could trigger Pause by pressing Ctrl+NumLock.
|
||||
// Now these are often physically separate and the two keys can be differentiated by
|
||||
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
|
||||
//
|
||||
// However in this event, both keys are reported as 0x0045 even on modern hardware.
|
||||
// Therefore we use the virtual key instead to determine whether it's a NumLock and
|
||||
// set the KeyCode accordingly.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
PhysicalKey::Code(KeyCode::NumLock)
|
||||
} else {
|
||||
PhysicalKey::from_scancode(scancode as u32)
|
||||
};
|
||||
if keyboard.VKey == VK_SHIFT {
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
match code {
|
||||
KeyCode::NumpadDecimal
|
||||
| KeyCode::Numpad0
|
||||
| KeyCode::Numpad1
|
||||
| KeyCode::Numpad2
|
||||
| KeyCode::Numpad3
|
||||
| KeyCode::Numpad4
|
||||
| KeyCode::Numpad5
|
||||
| KeyCode::Numpad6
|
||||
| KeyCode::Numpad7
|
||||
| KeyCode::Numpad8
|
||||
| KeyCode::Numpad9 => {
|
||||
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
|
||||
// wasn't active. The way this is exposed to applications by the system is that
|
||||
// the application receives a fake key release event for the shift key at the
|
||||
// moment when the numpad key is pressed, just before receiving the numpad key
|
||||
// as well.
|
||||
//
|
||||
// The issue is that in the raw device event (here), the fake shift release
|
||||
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
|
||||
// have any information to tell whether it's the left shift or the right shift
|
||||
// that needs to get the fake release (or press) event so we don't forward this
|
||||
// event to the application at all.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "The shift key overrides NumLock"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
userdata.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(RawKeyEvent {
|
||||
physical_key,
|
||||
state,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,10 @@ impl<T> EventLoopRunner<T> {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
pub fn clear_exit(&self) {
|
||||
self.exit.set(None);
|
||||
}
|
||||
|
||||
pub fn should_buffer(&self) -> bool {
|
||||
let handler = self.event_handler.take();
|
||||
let should_buffer = handler.is_none();
|
||||
@@ -399,23 +403,29 @@ impl<T> BufferedEvent<T> {
|
||||
match self {
|
||||
Self::Event(event) => dispatch(event),
|
||||
Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => {
|
||||
let new_inner_size = Arc::new(Mutex::new(new_inner_size));
|
||||
let user_new_innner_size = Arc::new(Mutex::new(new_inner_size));
|
||||
dispatch(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
|
||||
&user_new_innner_size,
|
||||
)),
|
||||
},
|
||||
});
|
||||
let inner_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let inner_size = *user_new_innner_size.lock().unwrap();
|
||||
|
||||
let window_flags = unsafe {
|
||||
let userdata =
|
||||
get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData<T>;
|
||||
(*userdata).window_state_lock().window_flags
|
||||
};
|
||||
window_flags.set_size((window_id.0).0, inner_size);
|
||||
drop(user_new_innner_size);
|
||||
|
||||
if inner_size != new_inner_size {
|
||||
let window_flags = unsafe {
|
||||
let userdata =
|
||||
get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData<T>;
|
||||
(*userdata).window_state_lock().window_flags
|
||||
};
|
||||
|
||||
window_flags.set_size((window_id.0).0, inner_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use windows_sys::Win32::{
|
||||
UI::{
|
||||
Input::Ime::{
|
||||
ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
|
||||
ImmSetCandidateWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM,
|
||||
CFS_EXCLUDE, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN,
|
||||
IACE_DEFAULT,
|
||||
ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED,
|
||||
ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM,
|
||||
GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
|
||||
},
|
||||
WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED},
|
||||
},
|
||||
@@ -124,7 +124,7 @@ impl ImeContext {
|
||||
left: x,
|
||||
top: y,
|
||||
right: x + width,
|
||||
bottom: y - height,
|
||||
bottom: y + height,
|
||||
};
|
||||
let candidate_form = CANDIDATEFORM {
|
||||
dwIndex: 0,
|
||||
@@ -132,8 +132,16 @@ impl ImeContext {
|
||||
ptCurrentPos: POINT { x, y },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
let composition_form = COMPOSITIONFORM {
|
||||
dwStyle: CFS_POINT,
|
||||
ptCurrentPos: POINT { x, y: y + height },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
|
||||
unsafe { ImmSetCandidateWindow(self.himc, &candidate_form) };
|
||||
unsafe {
|
||||
ImmSetCompositionWindow(self.himc, &composition_form);
|
||||
ImmSetCandidateWindow(self.himc, &candidate_form);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) {
|
||||
|
||||
@@ -52,12 +52,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
|
||||
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
|
||||
|
||||
// Cursor name in UTF-16. Used to set cursor in `WM_SETCURSOR`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cursor(pub *const u16);
|
||||
unsafe impl Send for Cursor {}
|
||||
unsafe impl Sync for Cursor {}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(u32);
|
||||
|
||||
|
||||
@@ -230,37 +230,45 @@ impl MonitorHandle {
|
||||
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
||||
// fields are probably changing, but we aren't looking at those fields
|
||||
// anyway), so we're using a BTreeSet deduplicate
|
||||
let mut modes = BTreeSet::new();
|
||||
let mut i = 0;
|
||||
let mut modes = BTreeSet::<RootVideoMode>::new();
|
||||
let mod_map = |mode: RootVideoMode| mode.video_mode;
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
let mut mode: DEVMODEW = mem::zeroed();
|
||||
mode.dmSize = mem::size_of_val(&mode) as u16;
|
||||
if EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == false.into() {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
|
||||
const REQUIRED_FIELDS: u32 =
|
||||
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
|
||||
|
||||
// Use Ord impl of RootVideoMode
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode {
|
||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: mode.dmBitsPerPel as u16,
|
||||
refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
|
||||
monitor: self.clone(),
|
||||
native_video_mode: Box::new(mode),
|
||||
},
|
||||
});
|
||||
let monitor_info = match get_monitor_info(self.0) {
|
||||
Ok(monitor_info) => monitor_info,
|
||||
Err(error) => {
|
||||
log::warn!("Error from get_monitor_info: {error}");
|
||||
return modes.into_iter().map(mod_map);
|
||||
}
|
||||
};
|
||||
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let mut mode: DEVMODEW = unsafe { mem::zeroed() };
|
||||
mode.dmSize = mem::size_of_val(&mode) as u16;
|
||||
if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() {
|
||||
break;
|
||||
}
|
||||
|
||||
const REQUIRED_FIELDS: u32 =
|
||||
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
|
||||
|
||||
// Use Ord impl of RootVideoMode
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode {
|
||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: mode.dmBitsPerPel as u16,
|
||||
refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
|
||||
monitor: self.clone(),
|
||||
native_video_mode: Box::new(mode),
|
||||
},
|
||||
});
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
modes.into_iter().map(|mode| mode.video_mode)
|
||||
modes.into_iter().map(mod_map)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,29 @@ use windows_sys::Win32::{
|
||||
UI::{
|
||||
Input::{
|
||||
GetRawInputData, GetRawInputDeviceInfoW, GetRawInputDeviceList,
|
||||
KeyboardAndMouse::{MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, VK_NUMLOCK, VK_SHIFT},
|
||||
RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST,
|
||||
RAWINPUTHEADER, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE, RIDI_DEVICEINFO,
|
||||
RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD,
|
||||
RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
RAWINPUTHEADER, RAWKEYBOARD, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE,
|
||||
RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID,
|
||||
RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID,
|
||||
RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_DOWN,
|
||||
RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP,
|
||||
RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_DOWN,
|
||||
RI_MOUSE_BUTTON_5_UP,
|
||||
RI_KEY_E0, RI_KEY_E1, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP,
|
||||
RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN,
|
||||
RI_MOUSE_BUTTON_3_UP, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP,
|
||||
RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{event::ElementState, event_loop::DeviceEvents, platform_impl::platform::util};
|
||||
use crate::{
|
||||
event::ElementState,
|
||||
event_loop::DeviceEvents,
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
platform::scancode::PhysicalKeyExtScancode,
|
||||
platform_impl::platform::util,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
||||
@@ -220,3 +228,99 @@ pub fn get_raw_mouse_button_state(button_flags: u32) -> [Option<ElementState>; 5
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option<PhysicalKey> {
|
||||
let extension = {
|
||||
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
|
||||
0xE000
|
||||
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
|
||||
0xE100
|
||||
} else {
|
||||
0x0000
|
||||
}
|
||||
};
|
||||
let scancode = if keyboard.MakeCode == 0 {
|
||||
// In some cases (often with media keys) the device reports a scancode of 0 but a
|
||||
// valid virtual key. In these cases we obtain the scancode from the virtual key.
|
||||
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
|
||||
} else {
|
||||
keyboard.MakeCode | extension
|
||||
};
|
||||
if scancode == 0xE11D || scancode == 0xE02A {
|
||||
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
|
||||
// Ctrl+NumLock.
|
||||
// This equvalence means that if the user presses Pause, the keyboard will emit two
|
||||
// subsequent keypresses:
|
||||
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
|
||||
// 2, 0x0045 - Which on its own can be interpreted as Pause
|
||||
//
|
||||
// There's another combination which isn't quite an equivalence:
|
||||
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
|
||||
// PrtSc (print screen) produces the following sequence:
|
||||
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
|
||||
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
|
||||
// its own it can be interpreted as PrtSc
|
||||
//
|
||||
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
|
||||
// that there's going to be another event coming, from which we can extract the
|
||||
// appropriate key.
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
return None;
|
||||
}
|
||||
let physical_key = if keyboard.VKey == VK_NUMLOCK {
|
||||
// Historically, the NumLock and the Pause key were one and the same physical key.
|
||||
// The user could trigger Pause by pressing Ctrl+NumLock.
|
||||
// Now these are often physically separate and the two keys can be differentiated by
|
||||
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
|
||||
//
|
||||
// However in this event, both keys are reported as 0x0045 even on modern hardware.
|
||||
// Therefore we use the virtual key instead to determine whether it's a NumLock and
|
||||
// set the KeyCode accordingly.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
PhysicalKey::Code(KeyCode::NumLock)
|
||||
} else {
|
||||
PhysicalKey::from_scancode(scancode as u32)
|
||||
};
|
||||
if keyboard.VKey == VK_SHIFT {
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
match code {
|
||||
KeyCode::NumpadDecimal
|
||||
| KeyCode::Numpad0
|
||||
| KeyCode::Numpad1
|
||||
| KeyCode::Numpad2
|
||||
| KeyCode::Numpad3
|
||||
| KeyCode::Numpad4
|
||||
| KeyCode::Numpad5
|
||||
| KeyCode::Numpad6
|
||||
| KeyCode::Numpad7
|
||||
| KeyCode::Numpad8
|
||||
| KeyCode::Numpad9 => {
|
||||
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
|
||||
// wasn't active. The way this is exposed to applications by the system is that
|
||||
// the application receives a fake key release event for the shift key at the
|
||||
// moment when the numpad key is pressed, just before receiving the numpad key
|
||||
// as well.
|
||||
//
|
||||
// The issue is that in the raw device event (here), the fake shift release
|
||||
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
|
||||
// have any information to tell whether it's the left shift or the right shift
|
||||
// that needs to get the fake release (or press) event so we don't forward this
|
||||
// event to the application at all.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "The shift key overrides NumLock"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
|
||||
return None;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(physical_key)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use once_cell::sync::Lazy;
|
||||
use windows_sys::{
|
||||
core::{HRESULT, PCWSTR},
|
||||
Win32::{
|
||||
Foundation::{BOOL, HMODULE, HWND, RECT},
|
||||
Foundation::{BOOL, HANDLE, HMODULE, HWND, RECT},
|
||||
Graphics::Gdi::{ClientToScreen, HMONITOR},
|
||||
System::{
|
||||
LibraryLoader::{GetProcAddress, LoadLibraryA},
|
||||
@@ -21,7 +21,10 @@ use windows_sys::{
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS},
|
||||
Input::KeyboardAndMouse::GetActiveWindow,
|
||||
Input::{
|
||||
KeyboardAndMouse::GetActiveWindow,
|
||||
Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO},
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement,
|
||||
GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS,
|
||||
@@ -191,7 +194,9 @@ pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to dynamically load function pointer.
|
||||
// Helper function to dynamically load function pointer as some functions
|
||||
// may not be available on all Windows platforms supported by winit.
|
||||
//
|
||||
// `library` and `function` must be zero-terminated.
|
||||
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
||||
assert_eq!(library.chars().last(), Some('\0'));
|
||||
@@ -237,6 +242,26 @@ pub type AdjustWindowRectExForDpi = unsafe extern "system" fn(
|
||||
dpi: u32,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointerId: u32,
|
||||
entriesCount: *mut u32,
|
||||
pointerCount: *mut u32,
|
||||
pointerInfo: *mut POINTER_INFO,
|
||||
) -> BOOL;
|
||||
|
||||
pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
|
||||
pub type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||
device: HANDLE,
|
||||
pointerDeviceRect: *mut RECT,
|
||||
displayRect: *mut RECT,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerTouchInfo =
|
||||
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||
|
||||
pub type GetPointerPenInfo =
|
||||
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
|
||||
|
||||
pub static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy<Option<AdjustWindowRectExForDpi>> =
|
||||
@@ -251,3 +276,13 @@ pub static SET_PROCESS_DPI_AWARENESS: Lazy<Option<SetProcessDpiAwareness>> =
|
||||
Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness));
|
||||
pub static SET_PROCESS_DPI_AWARE: Lazy<Option<SetProcessDPIAware>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware));
|
||||
pub static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
|
||||
pub static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
|
||||
pub static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||
pub static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||
pub static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));
|
||||
|
||||
@@ -83,7 +83,7 @@ use crate::{
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub(crate) struct Window {
|
||||
/// Main handle for the window.
|
||||
window: WindowWrapper,
|
||||
window: HWND,
|
||||
|
||||
/// The current window state.
|
||||
window_state: Arc<Mutex<WindowState>>,
|
||||
@@ -126,17 +126,26 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_transparent(&self, _transparent: bool) {}
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::TRANSPARENT, transparent)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_blur(&self, _blur: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::VISIBLE, visible)
|
||||
});
|
||||
});
|
||||
@@ -144,7 +153,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
Some(unsafe { IsWindowVisible(self.window.0) == 1 })
|
||||
Some(unsafe { IsWindowVisible(self.window) == 1 })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -180,10 +189,10 @@ impl Window {
|
||||
let (x, y): (i32, i32) = position.to_physical::<i32>(self.scale_factor()).into();
|
||||
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -237,10 +246,10 @@ impl Window {
|
||||
|
||||
if physical_size != self.inner_size() {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -275,12 +284,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::RESIZABLE, resizable)
|
||||
});
|
||||
});
|
||||
@@ -294,12 +303,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::MINIMIZABLE,
|
||||
buttons.contains(WindowButtons::MINIMIZE),
|
||||
@@ -333,14 +342,14 @@ impl Window {
|
||||
/// Returns the `hwnd` of this window.
|
||||
#[inline]
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window.0
|
||||
self.window
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_04")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut window_handle = rwh_04::Win32Handle::empty();
|
||||
window_handle.hwnd = self.window.0 as *mut _;
|
||||
window_handle.hwnd = self.window as *mut _;
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = hinstance as *mut _;
|
||||
rwh_04::RawWindowHandle::Win32(window_handle)
|
||||
@@ -350,7 +359,7 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::Win32WindowHandle::empty();
|
||||
window_handle.hwnd = self.window.0 as *mut _;
|
||||
window_handle.hwnd = self.window as *mut _;
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = hinstance as *mut _;
|
||||
rwh_05::RawWindowHandle::Win32(window_handle)
|
||||
@@ -367,8 +376,7 @@ impl Window {
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe {
|
||||
// SAFETY: Handle will never be zero.
|
||||
let window = self.window.0;
|
||||
std::num::NonZeroIsize::new_unchecked(window)
|
||||
std::num::NonZeroIsize::new_unchecked(self.window)
|
||||
});
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = std::num::NonZeroIsize::new(hinstance);
|
||||
@@ -404,7 +412,7 @@ impl Window {
|
||||
}
|
||||
};
|
||||
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
@@ -414,7 +422,7 @@ impl Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.map_err(|e| ExternalError::Os(os_error!(e)));
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
@@ -423,7 +431,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
@@ -433,7 +441,7 @@ impl Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, !visible))
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible))
|
||||
.map_err(|e| e.to_string());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
@@ -463,27 +471,41 @@ impl Window {
|
||||
}
|
||||
|
||||
unsafe fn handle_os_dragging(&self, wparam: WPARAM) {
|
||||
let points = {
|
||||
let mut pos = unsafe { mem::zeroed() };
|
||||
unsafe { GetCursorPos(&mut pos) };
|
||||
pos
|
||||
};
|
||||
let points = POINTS {
|
||||
x: points.x as i16,
|
||||
y: points.y as i16,
|
||||
};
|
||||
unsafe { ReleaseCapture() };
|
||||
let window = self.window;
|
||||
let window_state = self.window_state.clone();
|
||||
|
||||
self.window_state_lock().dragging = true;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
{
|
||||
let mut guard = window_state.lock().unwrap();
|
||||
if !guard.dragging {
|
||||
guard.dragging = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
PostMessageW(
|
||||
self.hwnd(),
|
||||
WM_NCLBUTTONDOWN,
|
||||
wparam,
|
||||
&points as *const _ as LPARAM,
|
||||
)
|
||||
};
|
||||
let points = {
|
||||
let mut pos = unsafe { mem::zeroed() };
|
||||
unsafe { GetCursorPos(&mut pos) };
|
||||
pos
|
||||
};
|
||||
let points = POINTS {
|
||||
x: points.x as i16,
|
||||
y: points.y as i16,
|
||||
};
|
||||
|
||||
// ReleaseCapture needs to execute on the main thread
|
||||
unsafe { ReleaseCapture() };
|
||||
|
||||
unsafe {
|
||||
PostMessageW(
|
||||
window,
|
||||
WM_NCLBUTTONDOWN,
|
||||
wparam,
|
||||
&points as *const _ as LPARAM,
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -592,10 +614,10 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest)
|
||||
});
|
||||
});
|
||||
@@ -610,7 +632,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
let is_minimized = util::is_minimized(self.hwnd());
|
||||
@@ -620,7 +642,7 @@ impl Window {
|
||||
WindowState::set_window_flags_in_place(&mut window_state.lock().unwrap(), |f| {
|
||||
f.set(WindowFlags::MINIMIZED, is_minimized)
|
||||
});
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MINIMIZED, minimized)
|
||||
});
|
||||
});
|
||||
@@ -633,12 +655,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, maximized)
|
||||
});
|
||||
});
|
||||
@@ -658,14 +680,24 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
let mut window_state_lock = window_state.lock().unwrap();
|
||||
let old_fullscreen = window_state_lock.fullscreen.clone();
|
||||
if window_state_lock.fullscreen == fullscreen {
|
||||
return;
|
||||
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
// Return if we already are in the same fullscreen mode
|
||||
_ if old_fullscreen == fullscreen => return,
|
||||
// Return if saved Borderless(monitor) is the same as current monitor when requested fullscreen is Borderless(None)
|
||||
(Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None)))
|
||||
if *monitor == monitor::current_monitor(window) =>
|
||||
{
|
||||
return
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
window_state_lock.fullscreen = fullscreen.clone();
|
||||
drop(window_state_lock);
|
||||
|
||||
@@ -728,7 +760,7 @@ impl Window {
|
||||
}
|
||||
|
||||
// Update window style
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
|
||||
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
|
||||
@@ -744,7 +776,7 @@ impl Window {
|
||||
// this needs to be called before the below fullscreen SetWindowPos as this itself
|
||||
// will generate WM_SIZE messages of the old window size that can race with what we set below
|
||||
unsafe {
|
||||
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
|
||||
taskbar_mark_fullscreen(window, fullscreen.is_some());
|
||||
}
|
||||
|
||||
// Update window bounds
|
||||
@@ -753,7 +785,7 @@ impl Window {
|
||||
// Save window bounds before entering fullscreen
|
||||
let placement = unsafe {
|
||||
let mut placement = mem::zeroed();
|
||||
GetWindowPlacement(window.0, &mut placement);
|
||||
GetWindowPlacement(window, &mut placement);
|
||||
placement
|
||||
};
|
||||
|
||||
@@ -762,7 +794,7 @@ impl Window {
|
||||
let monitor = match &fullscreen {
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window.0),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window),
|
||||
};
|
||||
|
||||
let position: (i32, i32) = monitor.position().into();
|
||||
@@ -770,7 +802,7 @@ impl Window {
|
||||
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
window.0,
|
||||
window,
|
||||
0,
|
||||
position.0,
|
||||
position.1,
|
||||
@@ -778,7 +810,7 @@ impl Window {
|
||||
size.1 as i32,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER,
|
||||
);
|
||||
InvalidateRgn(window.0, 0, false.into());
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -786,8 +818,8 @@ impl Window {
|
||||
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
|
||||
drop(window_state_lock);
|
||||
unsafe {
|
||||
SetWindowPlacement(window.0, &placement);
|
||||
InvalidateRgn(window.0, 0, false.into());
|
||||
SetWindowPlacement(window, &placement);
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -797,12 +829,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MARKER_DECORATIONS, decorations)
|
||||
});
|
||||
});
|
||||
@@ -818,12 +850,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, level: WindowLevel) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::ALWAYS_ON_TOP,
|
||||
level == WindowLevel::AlwaysOnTop,
|
||||
@@ -872,21 +904,21 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let state = self.window_state.clone();
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let scale_factor = state.lock().unwrap().scale_factor;
|
||||
ImeContext::current(window.0).set_ime_cursor_area(spot, size, scale_factor);
|
||||
ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let state = self.window_state.clone();
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
state.lock().unwrap().ime_allowed = allowed;
|
||||
ImeContext::set_ime_allowed(window.0, allowed);
|
||||
ImeContext::set_ime_allowed(window, allowed);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -895,14 +927,13 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let active_window_handle = unsafe { GetActiveWindow() };
|
||||
if window.0 == active_window_handle {
|
||||
if window == active_window_handle {
|
||||
return;
|
||||
}
|
||||
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let _ = &window;
|
||||
let (flags, count) = request_type
|
||||
.map(|ty| match ty {
|
||||
UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX),
|
||||
@@ -912,7 +943,7 @@ impl Window {
|
||||
|
||||
let flash_info = FLASHWINFO {
|
||||
cbSize: mem::size_of::<FLASHWINFO>() as u32,
|
||||
hwnd: window.0,
|
||||
hwnd: window,
|
||||
dwFlags: flags,
|
||||
uCount: count,
|
||||
dwTimeout: 0,
|
||||
@@ -923,7 +954,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
try_theme(self.window.0, theme);
|
||||
try_theme(self.window, theme);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -938,9 +969,9 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
let len = unsafe { GetWindowTextLengthW(self.window.0) } + 1;
|
||||
let len = unsafe { GetWindowTextLengthW(self.window) } + 1;
|
||||
let mut buf = vec![0; len as usize];
|
||||
unsafe { GetWindowTextW(self.window.0, buf.as_mut_ptr(), len) };
|
||||
unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) };
|
||||
util::decode_wide(&buf).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
@@ -952,12 +983,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_undecorated_shadow(&self, shadow: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow)
|
||||
});
|
||||
});
|
||||
@@ -965,15 +996,14 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
let window = self.window.clone();
|
||||
let window_flags = self.window_state_lock().window_flags();
|
||||
|
||||
let is_visible = window_flags.contains(WindowFlags::VISIBLE);
|
||||
let is_minimized = util::is_minimized(self.hwnd());
|
||||
let is_foreground = window.0 == unsafe { GetForegroundWindow() };
|
||||
let is_foreground = self.window == unsafe { GetForegroundWindow() };
|
||||
|
||||
if is_visible && !is_minimized && !is_foreground {
|
||||
unsafe { force_window_active(window.0) };
|
||||
unsafe { force_window_active(self.window) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1023,18 +1053,6 @@ impl Drop for Window {
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple non-owning wrapper around a window.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct WindowWrapper(HWND);
|
||||
|
||||
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
unsafe impl Sync for WindowWrapper {}
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
pub(super) struct InitData<'a, T: 'static> {
|
||||
// inputs
|
||||
pub event_loop: &'a EventLoopWindowTarget<T>,
|
||||
@@ -1082,7 +1100,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
unsafe { ImeContext::set_ime_allowed(window, false) };
|
||||
|
||||
Window {
|
||||
window: WindowWrapper(window),
|
||||
window,
|
||||
window_state,
|
||||
thread_executor: self.event_loop.create_thread_executor(),
|
||||
}
|
||||
@@ -1105,7 +1123,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
|
||||
let file_drop_runner = self.event_loop.runner_shared.clone();
|
||||
let file_drop_handler = FileDropHandler::new(
|
||||
win.window.0,
|
||||
win.window,
|
||||
Box::new(move |event| {
|
||||
if let Ok(e) = event.map_nonuser_event() {
|
||||
file_drop_runner.send_event(e)
|
||||
@@ -1117,7 +1135,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void };
|
||||
|
||||
assert_eq!(
|
||||
unsafe { RegisterDragDrop(win.window.0, handler_interface_ptr) },
|
||||
unsafe { RegisterDragDrop(win.window, handler_interface_ptr) },
|
||||
S_OK
|
||||
);
|
||||
Some(file_drop_handler)
|
||||
@@ -1192,28 +1210,17 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
|
||||
win.set_enabled_buttons(attributes.enabled_buttons);
|
||||
|
||||
if attributes.fullscreen.0.is_some() {
|
||||
win.set_fullscreen(attributes.fullscreen.0.map(Into::into));
|
||||
unsafe { force_window_active(win.window.0) };
|
||||
} else {
|
||||
let size = attributes
|
||||
.inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(800, 600).into());
|
||||
let max_size = attributes
|
||||
.max_inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into());
|
||||
let min_size = attributes
|
||||
.min_inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(0, 0).into());
|
||||
let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor());
|
||||
win.request_inner_size(clamped_size);
|
||||
|
||||
if attributes.maximized {
|
||||
// Need to set MAXIMIZED after setting `inner_size` as
|
||||
// `Window::request_inner_size` changes MAXIMIZED to false.
|
||||
win.set_maximized(true);
|
||||
}
|
||||
}
|
||||
let size = attributes
|
||||
.inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(800, 600).into());
|
||||
let max_size = attributes
|
||||
.max_inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into());
|
||||
let min_size = attributes
|
||||
.min_inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(0, 0).into());
|
||||
let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor());
|
||||
win.request_inner_size(clamped_size);
|
||||
|
||||
// let margins = MARGINS {
|
||||
// cxLeftWidth: 1,
|
||||
@@ -1294,6 +1301,9 @@ where
|
||||
#[cfg(not(feature = "rwh_06"))]
|
||||
let parent = fallback_parent();
|
||||
|
||||
let fullscreen = attributes.fullscreen.clone();
|
||||
let maximized = attributes.maximized;
|
||||
|
||||
let mut initdata = InitData {
|
||||
event_loop,
|
||||
attributes,
|
||||
@@ -1331,7 +1341,18 @@ where
|
||||
|
||||
// If the handle is non-null, then window creation must have succeeded, which means
|
||||
// that we *must* have populated the `InitData.window` field.
|
||||
Ok(initdata.window.unwrap())
|
||||
let win = initdata.window.unwrap();
|
||||
|
||||
// Need to set FULLSCREEN or MAXIMIZED after CreateWindowEx
|
||||
// This is because if the size is changed in WM_CREATE, the restored size will be stored in that size.
|
||||
if fullscreen.0.is_some() {
|
||||
win.set_fullscreen(fullscreen.0.map(Into::into));
|
||||
unsafe { force_window_active(win.window) };
|
||||
} else if maximized {
|
||||
win.set_maximized(true);
|
||||
}
|
||||
|
||||
Ok(win)
|
||||
}
|
||||
|
||||
unsafe fn register_window_class<T: 'static>(class_name: &[u16]) {
|
||||
@@ -1357,7 +1378,7 @@ unsafe fn register_window_class<T: 'static>(class_name: &[u16]) {
|
||||
unsafe { RegisterClassExW(&class) };
|
||||
}
|
||||
|
||||
struct ComInitialized(*mut ());
|
||||
struct ComInitialized(#[allow(dead_code)] *mut ());
|
||||
impl Drop for ComInitialized {
|
||||
fn drop(&mut self) {
|
||||
unsafe { CoUninitialize() };
|
||||
|
||||
@@ -543,14 +543,17 @@ impl Window {
|
||||
self.window.maybe_wait_on_main(|w| WindowId(w.id()))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and
|
||||
/// vice versa.
|
||||
///
|
||||
/// Note that this value can change depending on user action (for example if the window is
|
||||
/// moved to another screen); as such, tracking [`WindowEvent::ScaleFactorChanged`] events is
|
||||
/// the most robust way to track the DPI you need to use to draw.
|
||||
///
|
||||
/// This value may differ from [`MonitorHandle::scale_factor`].
|
||||
///
|
||||
/// See the [`dpi`](crate::dpi) module for more information.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
|
||||
@@ -597,11 +600,11 @@ impl Window {
|
||||
self.window.maybe_queue_on_main(|w| w.request_redraw())
|
||||
}
|
||||
|
||||
/// Notify the windowing system that you're before presenting to the window.
|
||||
/// Notify the windowing system before presenting to the window.
|
||||
///
|
||||
/// You should call this event after you've done drawing operations, but before you submit
|
||||
/// You should call this event after your drawing operations, but before you submit
|
||||
/// the buffer to the display or commit your drawings. Doing so will help winit to properly
|
||||
/// schedule and do assumptions about its internal state. For example, it could properly
|
||||
/// schedule and make assumptions about its internal state. For example, it could properly
|
||||
/// throttle [`WindowEvent::RedrawRequested`].
|
||||
///
|
||||
/// ## Example
|
||||
@@ -904,7 +907,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows / Web / iOS / Android / Orbital:** Unsupported.
|
||||
/// - **Web / iOS / Android:** Unsupported.
|
||||
/// - **X11:** Can only be set while building the window, with [`WindowBuilder::with_transparent`].
|
||||
#[inline]
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
@@ -918,7 +921,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Android / iOS / macOS / X11 / Web / Windows:** Unsupported.
|
||||
/// - **Android / iOS / X11 / Web / Windows:** Unsupported.
|
||||
/// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
|
||||
#[inline]
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
@@ -1037,7 +1040,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
||||
/// - **iOS / Android / Web:** Unsupported.
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
self.window
|
||||
@@ -1048,7 +1051,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
||||
/// - **iOS / Android / Web:** Unsupported.
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
self.window.maybe_wait_on_main(|w| w.is_maximized())
|
||||
@@ -1360,7 +1363,8 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Wayland / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
/// - **Wayland**: Cursor must be in [`CursorGrabMode::Locked`].
|
||||
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
#[inline]
|
||||
pub fn set_cursor_position<P: Into<Position>>(&self, position: P) -> Result<(), ExternalError> {
|
||||
let position = position.into();
|
||||
@@ -1368,7 +1372,7 @@ impl Window {
|
||||
.maybe_wait_on_main(|w| w.set_cursor_position(position))
|
||||
}
|
||||
|
||||
/// Set grabbing [mode]([`CursorGrabMode`]) on the cursor preventing it from leaving the window.
|
||||
/// Set grabbing [mode](CursorGrabMode) on the cursor preventing it from leaving the window.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -1399,7 +1403,7 @@ impl Window {
|
||||
/// - **Wayland:** The cursor is only hidden within the confines of the window.
|
||||
/// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
|
||||
/// outside of the window.
|
||||
/// - **iOS / Android / Orbital:** Unsupported.
|
||||
/// - **iOS / Android:** Unsupported.
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.window
|
||||
@@ -1416,7 +1420,7 @@ impl Window {
|
||||
/// - **X11:** Un-grabs the cursor.
|
||||
/// - **Wayland:** Requires the cursor to be inside the window to be dragged.
|
||||
/// - **macOS:** May prevent the button release event to be triggered.
|
||||
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
self.window.maybe_wait_on_main(|w| w.drag_window())
|
||||
@@ -1430,7 +1434,7 @@ impl Window {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Always returns an [`ExternalError::NotSupported`]
|
||||
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
|
||||
#[inline]
|
||||
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
|
||||
self.window
|
||||
@@ -1472,10 +1476,6 @@ impl Window {
|
||||
/// Returns the monitor on which the window currently resides.
|
||||
///
|
||||
/// Returns `None` if current monitor can't be detected.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
self.window
|
||||
@@ -1486,10 +1486,6 @@ impl Window {
|
||||
///
|
||||
/// This is the same as [`EventLoopWindowTarget::available_monitors`], and is provided for convenience.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
///
|
||||
/// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
@@ -1508,8 +1504,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
/// **Wayland:** Always returns `None`.
|
||||
/// **Wayland / Web:** Always returns `None`.
|
||||
///
|
||||
/// [`EventLoopWindowTarget::primary_monitor`]: crate::event_loop::EventLoopWindowTarget::primary_monitor
|
||||
#[inline]
|
||||
@@ -1593,7 +1588,7 @@ pub enum CursorGrabMode {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
|
||||
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
|
||||
Confined,
|
||||
|
||||
/// The cursor is locked inside the window area to the certain position.
|
||||
@@ -1604,7 +1599,7 @@ pub enum CursorGrabMode {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
|
||||
/// - **iOS / Android / Orbital:** Always returns an [`ExternalError::NotSupported`].
|
||||
/// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`].
|
||||
Locked,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user