mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
205 Commits
v0.29.x
...
notgull/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5240c4650 | ||
|
|
1c65f70d59 | ||
|
|
4a30d0eadd | ||
|
|
39b5437951 | ||
|
|
9477514ad5 | ||
|
|
5c9f1b2e9a | ||
|
|
cc33212479 | ||
|
|
f2c5127f27 | ||
|
|
af93167237 | ||
|
|
7f6b16a6af | ||
|
|
bf5806a9b2 | ||
|
|
2a9c593e01 | ||
|
|
becdd0dbd2 | ||
|
|
3eea505440 | ||
|
|
b863283c38 | ||
|
|
73718c9f2f | ||
|
|
f735f028a1 | ||
|
|
da947992ac | ||
|
|
e9784127df | ||
|
|
0be2bb0a8c | ||
|
|
075996b1fa | ||
|
|
a7241b3db3 | ||
|
|
17296e9878 | ||
|
|
b3c87caa7c | ||
|
|
81a1d9c396 | ||
|
|
5612626944 | ||
|
|
d3ca685b77 | ||
|
|
7bed5eecfd | ||
|
|
14140607d1 | ||
|
|
eab982c402 | ||
|
|
21701a33de | ||
|
|
c89e6df758 | ||
|
|
e9210555c1 | ||
|
|
0994b5ceb8 | ||
|
|
bcce5134e1 | ||
|
|
d333dd8664 | ||
|
|
52af1b4a77 | ||
|
|
3c9f9da19e | ||
|
|
075dfcea19 | ||
|
|
92b7dcccc1 | ||
|
|
5a3be586f4 | ||
|
|
12dbbf8012 | ||
|
|
53ca5af48f | ||
|
|
c235bd154a | ||
|
|
f4e71a1d9c | ||
|
|
62ed51a138 | ||
|
|
b2a2ec91ae | ||
|
|
d37d1a03b2 | ||
|
|
772b21ce09 | ||
|
|
2edcd09704 | ||
|
|
d35c3bea42 | ||
|
|
89a184ed84 | ||
|
|
36d4907da8 | ||
|
|
98b3508aca | ||
|
|
c0db53a516 | ||
|
|
52b7205b75 | ||
|
|
41dbbc27a0 | ||
|
|
c346fb7e61 | ||
|
|
6a041f84ba | ||
|
|
acfeff5327 | ||
|
|
b9e1e96eaa | ||
|
|
880238a24f | ||
|
|
f5b4d6938f | ||
|
|
c65e2247a1 | ||
|
|
801fddbfcf | ||
|
|
3ad64fb811 | ||
|
|
9bf4493a21 | ||
|
|
48f6582eb4 | ||
|
|
ef34692148 | ||
|
|
c48116a8fd | ||
|
|
b938fe9df5 | ||
|
|
b7e3649e8b | ||
|
|
844269d017 | ||
|
|
e41fac825c | ||
|
|
bbeacc46d5 | ||
|
|
61581ebb4f | ||
|
|
93f1000a05 | ||
|
|
1ea41a2ee2 | ||
|
|
42c9b7e40e | ||
|
|
0960635895 | ||
|
|
789a497980 | ||
|
|
fac6110cb6 | ||
|
|
0363be4776 | ||
|
|
f5dd1c008c | ||
|
|
48a1e84906 | ||
|
|
ee0db52ac4 | ||
|
|
c7cf0cfd83 | ||
|
|
8393d98940 | ||
|
|
af247eac0f | ||
|
|
b2b4564a5f | ||
|
|
cb58c49a90 | ||
|
|
ffb46dd61f | ||
|
|
8c8fb39fcd | ||
|
|
2422ea39d0 | ||
|
|
878d832d24 | ||
|
|
e2e01e1fc6 | ||
|
|
c8b685ddbc | ||
|
|
f10ae52385 | ||
|
|
e731041c15 | ||
|
|
9df7fc47a1 | ||
|
|
992aeb0ca0 | ||
|
|
0caba93b51 | ||
|
|
c00c1e9eb7 | ||
|
|
83950acd5a | ||
|
|
b99403b1b9 | ||
|
|
4f0ce7201d | ||
|
|
e648169861 | ||
|
|
8fdd81ecef | ||
|
|
7a2a2341c2 | ||
|
|
d68d9eab38 | ||
|
|
a06ea45c0f | ||
|
|
67b041e231 | ||
|
|
6dfc78fb50 | ||
|
|
477619c0a7 | ||
|
|
5f1a4b65ad | ||
|
|
bb9b629bc3 | ||
|
|
0c8cf94a70 | ||
|
|
1dfca5a395 | ||
|
|
7541220a41 | ||
|
|
7e11912d22 | ||
|
|
86baa1c99a | ||
|
|
d9f04780cc | ||
|
|
67d3fd28f7 | ||
|
|
a3cba838ea | ||
|
|
48abf52aac | ||
|
|
68ef9f707e | ||
|
|
9979441c82 | ||
|
|
309e6aa85a | ||
|
|
8b8556798e | ||
|
|
2d96480a89 | ||
|
|
6caff77abb | ||
|
|
af6c343d0e | ||
|
|
119462795a | ||
|
|
f801c4a00b | ||
|
|
f9758528f6 | ||
|
|
778d70c001 | ||
|
|
dc973883c9 | ||
|
|
2233edb9a0 | ||
|
|
bd2f1e8312 | ||
|
|
e9ebf1e5f4 | ||
|
|
5f7955cb2b | ||
|
|
793c535b01 | ||
|
|
ed26dd58fd | ||
|
|
584aab4cd0 | ||
|
|
8100a6a584 | ||
|
|
cad3277550 | ||
|
|
3c3a863cc9 | ||
|
|
8a7e18aaf0 | ||
|
|
57fad2ce15 | ||
|
|
7a58fe58ce | ||
|
|
38f28d5836 | ||
|
|
189a0080a6 | ||
|
|
b5aa96bea4 | ||
|
|
19e3906369 | ||
|
|
9ac3259a79 | ||
|
|
2b2dd6b65d | ||
|
|
75173118b0 | ||
|
|
e33d2bee6c | ||
|
|
ae7497e18f | ||
|
|
935146d299 | ||
|
|
755c533b08 | ||
|
|
ae9b02e097 | ||
|
|
e6c7cc297d | ||
|
|
b74cee8df1 | ||
|
|
e5eb253698 | ||
|
|
ec11b4877f | ||
|
|
9e46dffcc5 | ||
|
|
7501039d57 | ||
|
|
289ce32d77 | ||
|
|
0d366ffbda | ||
|
|
a6f414d732 | ||
|
|
c47d0846fa | ||
|
|
461efaf99f | ||
|
|
420840278b | ||
|
|
f5e73b0af4 | ||
|
|
f40b5f0dad | ||
|
|
c91402efb9 | ||
|
|
43acf7f42f | ||
|
|
c62e64060b | ||
|
|
f7a84a5b50 | ||
|
|
89aa7cc06e | ||
|
|
b166e1ff13 | ||
|
|
97434d8d80 | ||
|
|
06fb089633 | ||
|
|
4d6dbea74c | ||
|
|
c5941d105f | ||
|
|
b63164645b | ||
|
|
5b5ebc25d8 | ||
|
|
d7ec899d69 | ||
|
|
5379d60e4d | ||
|
|
44e2f95331 | ||
|
|
3b2d1a7643 | ||
|
|
50b17a3907 | ||
|
|
af26f01b95 | ||
|
|
c4d70d75c1 | ||
|
|
db8de03142 | ||
|
|
ff0ce9d065 | ||
|
|
42e492cde8 | ||
|
|
5e0e1e96bc | ||
|
|
bd890e69aa | ||
|
|
bca57ed0b4 | ||
|
|
4652d48105 | ||
|
|
96c0b267e2 | ||
|
|
81fd39485f | ||
|
|
6178acede8 |
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 && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
|
||||
99
CHANGELOG.md
99
CHANGELOG.md
@@ -11,95 +11,12 @@ Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.29.15
|
||||
|
||||
- On X11, fix crash due to xsettings query on systems with incomplete xsettings.
|
||||
|
||||
# 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 Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
|
||||
- Add `Window::set_custom_cursor`
|
||||
- Add `CustomCursor`
|
||||
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
||||
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
||||
- On macOS, add services menu.
|
||||
- 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.
|
||||
@@ -108,8 +25,8 @@ Unreleased` header.
|
||||
- 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`.
|
||||
- On Web, remove queuing fullscreen request in absence of transient activation.
|
||||
- On Web, fix setting cursor icon overriding cursor visibility.
|
||||
|
||||
# 0.29.4
|
||||
|
||||
|
||||
23
Cargo.toml
23
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.15"
|
||||
version = "0.29.4"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -43,7 +43,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", "xim"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
@@ -66,7 +66,7 @@ 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.2", features = ["std"], optional = true }
|
||||
rwh_05 = { package = "raw-window-handle", version = "0.5", 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"
|
||||
@@ -165,9 +165,10 @@ wayland-backend = { version = "0.3.0", default_features = false, features = ["cl
|
||||
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.19.1", optional = true }
|
||||
x11-dl = { version = "2.18.5", 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"
|
||||
xim = { version = "0.3.0", features = ["x11rb-client", "client"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
orbclient = { version = "0.3.42", default-features = false }
|
||||
@@ -179,6 +180,7 @@ version = "0.3.64"
|
||||
features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'Blob',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
@@ -190,6 +192,10 @@ features = [
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'ImageBitmap',
|
||||
'ImageBitmapOptions',
|
||||
'ImageBitmapRenderingContext',
|
||||
'ImageData',
|
||||
'IntersectionObserver',
|
||||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
@@ -199,6 +205,7 @@ features = [
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
'PremultiplyAlpha',
|
||||
'ResizeObserver',
|
||||
'ResizeObserverBoxOptions',
|
||||
'ResizeObserverEntry',
|
||||
@@ -206,7 +213,8 @@ features = [
|
||||
'ResizeObserverSize',
|
||||
'VisibilityState',
|
||||
'Window',
|
||||
'WheelEvent'
|
||||
'WheelEvent',
|
||||
'Url',
|
||||
]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
@@ -224,3 +232,6 @@ web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
||||
members = [
|
||||
"run-wasm",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
xim = { git = "https://github.com/forkgull/xim-rs", branch = "x11rb-13" }
|
||||
|
||||
@@ -106,6 +106,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
||||
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
||||
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
|
||||
- **Cursor image**: Changing the cursor to your own image.
|
||||
- **Cursor hittest**: Handle or ignore mouse events for a window.
|
||||
- **Touch events**: Single-touch events.
|
||||
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
||||
@@ -206,6 +207,7 @@ Legend:
|
||||
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|
||||
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|
||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.15"
|
||||
winit = "0.29.4"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -150,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/rust-mobile/android-activity/tree/main/examples).
|
||||
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
|
||||
|
||||
##### Converting from `ndk-glue` to `android-activity`
|
||||
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
|
||||
1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.15", features = [ "android-native-activity" ] }`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.4", 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).
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ disallowed-methods = [
|
||||
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
|
||||
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||
|
||||
92
examples/custom_cursors.rs
Normal file
92
examples/custom_cursors.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
#![allow(clippy::single_match, clippy::disallowed_methods)]
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{CustomCursor, WindowBuilder},
|
||||
};
|
||||
|
||||
fn decode_cursor(bytes: &[u8]) -> CustomCursor {
|
||||
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
|
||||
let samples = img.into_flat_samples();
|
||||
let (_, w, h) = samples.extents();
|
||||
let (w, h) = (w as u16, h as u16);
|
||||
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
#[cfg(not(wasm_platform))]
|
||||
SimpleLogger::new()
|
||||
.with_level(log::LevelFilter::Info)
|
||||
.init()
|
||||
.unwrap();
|
||||
#[cfg(wasm_platform)]
|
||||
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let builder = WindowBuilder::new().with_title("A fantastic window!");
|
||||
#[cfg(wasm_platform)]
|
||||
let builder = {
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
builder.with_append(true)
|
||||
};
|
||||
let window = builder.build(&event_loop).unwrap();
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
let mut cursor_visible = true;
|
||||
|
||||
let custom_cursors = [
|
||||
decode_cursor(include_bytes!("data/cross.png")),
|
||||
decode_cursor(include_bytes!("data/cross2.png")),
|
||||
];
|
||||
|
||||
event_loop.run(move |event, _elwt| match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
state: ElementState::Pressed,
|
||||
logical_key: key,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("1") => {
|
||||
log::debug!("Setting cursor to {:?}", cursor_idx);
|
||||
window.set_custom_cursor(&custom_cursors[cursor_idx]);
|
||||
cursor_idx = (cursor_idx + 1) % 2;
|
||||
}
|
||||
Key::Character("2") => {
|
||||
log::debug!("Setting cursor icon to default");
|
||||
window.set_cursor_icon(Default::default());
|
||||
}
|
||||
Key::Character("3") => {
|
||||
cursor_visible = !cursor_visible;
|
||||
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
|
||||
window.set_cursor_visible(cursor_visible);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
#[cfg(not(wasm_platform))]
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
#[cfg(not(wasm_platform))]
|
||||
_elwt.exit();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
BIN
examples/data/cross.png
Normal file
BIN
examples/data/cross.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 B |
BIN
examples/data/cross2.png
Normal file
BIN
examples/data/cross2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 B |
@@ -7,30 +7,17 @@
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
pub use platform::fill_window;
|
||||
use winit::window::Window;
|
||||
|
||||
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
||||
mod platform {
|
||||
pub(super) fn fill_window(window: &Window) {
|
||||
use softbuffer::{Context, Surface};
|
||||
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.
|
||||
@@ -48,69 +35,55 @@ mod platform {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_surface(&mut self, window: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, window) }
|
||||
fn surface(&mut self, w: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(w.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, w) }
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
})
|
||||
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));
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
});
|
||||
}
|
||||
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))
|
||||
.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");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
||||
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.
|
||||
}
|
||||
pub(super) fn fill_window(_window: &Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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,7 +40,6 @@ 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(),
|
||||
|
||||
198
src/cursor.rs
Normal file
198
src/cursor.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use core::fmt;
|
||||
use std::{error::Error, sync::Arc};
|
||||
|
||||
use crate::platform_impl::PlatformCustomCursor;
|
||||
|
||||
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
|
||||
pub const MAX_CURSOR_SIZE: u16 = 2048;
|
||||
|
||||
const PIXEL_SIZE: usize = 4;
|
||||
|
||||
/// Use a custom image as a cursor (mouse pointer).
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use winit::window::CustomCursor;
|
||||
///
|
||||
/// let w = 10;
|
||||
/// let h = 10;
|
||||
/// let rgba = vec![255; (w * h * 4) as usize];
|
||||
/// let custom_cursor = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
|
||||
///
|
||||
/// #[cfg(target_family = "wasm")]
|
||||
/// let custom_cursor_url = {
|
||||
/// use winit::platform::web::CustomCursorExtWebSys;
|
||||
/// CustomCursor::from_url("http://localhost:3000/cursor.png", 0, 0).unwrap()
|
||||
/// };
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CustomCursor {
|
||||
pub(crate) inner: Arc<PlatformCustomCursor>,
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
/// Creates a new cursor from an rgba buffer.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Setting cursor could be delayed due to the creation of `Blob` objects,
|
||||
/// which are async by nature.
|
||||
pub fn from_rgba(
|
||||
rgba: impl Into<Vec<u8>>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
Ok(Self {
|
||||
inner: PlatformCustomCursor::from_rgba(
|
||||
rgba.into(),
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
)?
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BadImage {
|
||||
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
|
||||
/// guarantee that the cursor will work, but should avoid many platform and device specific
|
||||
/// limits.
|
||||
TooLarge { width: u16, height: u16 },
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
ByteCountNotDivisibleBy4 { byte_count: usize },
|
||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
||||
/// At least one of your arguments is incorrect.
|
||||
DimensionsVsPixelCount {
|
||||
width: u16,
|
||||
height: u16,
|
||||
width_x_height: u64,
|
||||
pixel_count: u64,
|
||||
},
|
||||
/// Produced when the hotspot is outside the image bounds
|
||||
HotspotOutOfBounds {
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for BadImage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BadImage::TooLarge { width, height } => write!(f,
|
||||
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
|
||||
),
|
||||
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
|
||||
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
),
|
||||
BadImage::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => write!(f,
|
||||
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
|
||||
),
|
||||
BadImage::HotspotOutOfBounds {
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => write!(f,
|
||||
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds ({width:?}x{height:?}).",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadImage {}
|
||||
|
||||
/// Platforms export this directly as `PlatformCustomCursor` if they need to only work with images.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CursorImage {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u16,
|
||||
pub(crate) height: u16,
|
||||
pub(crate) hotspot_x: u16,
|
||||
pub(crate) hotspot_y: u16,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl CursorImage {
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
|
||||
return Err(BadImage::TooLarge { width, height });
|
||||
}
|
||||
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadImage::ByteCountNotDivisibleBy4 {
|
||||
byte_count: rgba.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
|
||||
let width_x_height = width as u64 * height as u64;
|
||||
if pixel_count != width_x_height {
|
||||
return Err(BadImage::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
});
|
||||
}
|
||||
|
||||
if hotspot_x >= width || hotspot_y >= height {
|
||||
return Err(BadImage::HotspotOutOfBounds {
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(CursorImage {
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct NoCustomCursor;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NoCustomCursor {
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
@@ -574,7 +574,7 @@ pub enum WindowEvent {
|
||||
/// ### Others
|
||||
///
|
||||
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
|
||||
/// - **Android / Wayland / Windows / Orbital:** Unsupported.
|
||||
/// - **Android / 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, AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::{error, fmt};
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
@@ -459,16 +459,16 @@ pub enum DeviceEvents {
|
||||
/// executed and removed from the list.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AsyncRequestSerial {
|
||||
serial: usize,
|
||||
serial: u64,
|
||||
}
|
||||
|
||||
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: 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.
|
||||
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||
// NOTE: we rely on wrap around here, while the user may just request
|
||||
// in the loop u64::MAX times that's issue is considered on them.
|
||||
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
|
||||
Self { serial }
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ extern crate bitflags;
|
||||
pub mod dpi;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
mod cursor;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
mod icon;
|
||||
|
||||
@@ -53,13 +53,5 @@ 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;
|
||||
|
||||
@@ -76,14 +76,6 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@
|
||||
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
|
||||
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::event_loop::EventLoopWindowTarget;
|
||||
use crate::platform_impl::PlatformCustomCursor;
|
||||
use crate::window::{Window, WindowBuilder};
|
||||
use crate::SendSyncWrapper;
|
||||
|
||||
@@ -200,3 +202,25 @@ pub enum PollStrategy {
|
||||
#[default]
|
||||
Scheduler,
|
||||
}
|
||||
|
||||
pub trait CustomCursorExtWebSys {
|
||||
/// Creates a new cursor from a URL pointing to an image.
|
||||
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
|
||||
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
|
||||
///
|
||||
/// [PNG]: https://en.wikipedia.org/wiki/PNG
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self;
|
||||
}
|
||||
|
||||
impl CustomCursorExtWebSys for CustomCursor {
|
||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self {
|
||||
Self {
|
||||
inner: PlatformCustomCursor::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) = "instance", "general"`.
|
||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
|
||||
///
|
||||
/// For details about application ID conventions, see the
|
||||
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
|
||||
|
||||
@@ -18,6 +18,7 @@ use android_activity::{
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
cursor::CustomCursor,
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
event::{self, Force, InnerSizeWriter, StartCause},
|
||||
@@ -713,10 +714,6 @@ 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()
|
||||
}
|
||||
@@ -910,6 +907,8 @@ impl Window {
|
||||
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub fn set_custom_cursor(&self, _: CustomCursor) {}
|
||||
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
@@ -1035,6 +1034,7 @@ impl Display for OsError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#![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};
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ pub(crate) use self::{
|
||||
};
|
||||
|
||||
use self::uikit::UIScreen;
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use super::app_state::EventWrapper;
|
||||
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
|
||||
use super::view::{WinitUIWindow, WinitView, WinitViewController};
|
||||
use crate::{
|
||||
cursor::CustomCursor,
|
||||
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, WindowEvent},
|
||||
@@ -177,6 +178,10 @@ impl Inner {
|
||||
debug!("`Window::set_cursor_icon` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_custom_cursor(&self, _: CustomCursor) {
|
||||
debug!("`Window::set_custom_cursor` ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
//! 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,
|
||||
};
|
||||
//! Convert XKB keys to Winit keys.
|
||||
|
||||
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.
|
||||
///
|
||||
@@ -522,7 +504,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::ArrowUp,
|
||||
keysyms::KP_Up => NamedKey::ArrowLeft,
|
||||
keysyms::KP_Right => NamedKey::ArrowRight,
|
||||
keysyms::KP_Down => NamedKey::ArrowDown,
|
||||
// keysyms::KP_Prior => NamedKey::PageUp,
|
||||
@@ -912,140 +894,3 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod xkb;
|
||||
pub mod keymap;
|
||||
pub mod xkb_state;
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
//! XKB compose handling.
|
||||
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use 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,465 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
//! 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(&self) -> PhysicalKey {
|
||||
fn physical_key(&mut self) -> PhysicalKey {
|
||||
keymap::raw_keycode_to_physicalkey(self.keycode)
|
||||
}
|
||||
|
||||
@@ -553,7 +553,6 @@ impl<'a> KeyEventResults<'a> {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.keysym_to_key(keysym)
|
||||
.unwrap_or_else(|(key, location)| {
|
||||
(
|
||||
@@ -566,7 +565,7 @@ impl<'a> KeyEventResults<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
fn keysym_to_key(&mut 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(_)) {
|
||||
|
||||
@@ -14,6 +14,7 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
|
||||
use once_cell::sync::Lazy;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform::x11::XlibErrorHook;
|
||||
use crate::{
|
||||
@@ -40,6 +41,7 @@ pub use x11::XNotSupported;
|
||||
#[cfg(x11_platform)]
|
||||
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
|
||||
|
||||
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
@@ -424,6 +426,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
|
||||
@@ -521,7 +528,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn reset_dead_keys(&self) {
|
||||
common::xkb::reset_dead_keys()
|
||||
common::xkb_state::reset_dead_keys()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -659,11 +666,11 @@ impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
|
||||
impl PhysicalKeyExtScancode for PhysicalKey {
|
||||
fn from_scancode(scancode: u32) -> PhysicalKey {
|
||||
common::xkb::scancode_to_keycode(scancode)
|
||||
common::keymap::scancode_to_keycode(scancode)
|
||||
}
|
||||
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
common::xkb::physicalkey_to_scancode(self)
|
||||
common::keymap::physicalkey_to_scancode(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,11 +764,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
let backend = match (
|
||||
attributes.forced_backend,
|
||||
env::var("WAYLAND_DISPLAY")
|
||||
.ok()
|
||||
.filter(|var| !var.is_empty())
|
||||
.or_else(|| env::var("WAYLAND_SOCKET").ok())
|
||||
.filter(|var| !var.is_empty())
|
||||
.is_some(),
|
||||
.map(|var| !var.is_empty())
|
||||
.unwrap_or(false),
|
||||
env::var("DISPLAY")
|
||||
.map(|var| !var.is_empty())
|
||||
.unwrap_or(false),
|
||||
@@ -775,15 +779,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
#[cfg(x11_platform)]
|
||||
(None, _, true) => Backend::X,
|
||||
// No backend is present.
|
||||
(_, 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))));
|
||||
_ => {
|
||||
return Err(EventLoopError::Os(os_error!(OsError::Misc(
|
||||
"neither WAYLAND_DISPLAY nor DISPLAY is set."
|
||||
))));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -792,7 +791,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 => EventLoop::new_x11_any_thread().map_err(Into::into),
|
||||
Backend::X => Ok(EventLoop::new_x11_any_thread().unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,10 +801,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
|
||||
Err(err) => return Err(err.clone()),
|
||||
};
|
||||
|
||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
||||
@@ -926,10 +925,6 @@ 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())
|
||||
}
|
||||
@@ -938,12 +933,10 @@ 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,12 +10,13 @@ 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;
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
use crate::error::{EventLoopError, OsError as RootOsError};
|
||||
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
use crate::event_loop::{
|
||||
@@ -33,7 +34,7 @@ use sink::EventSink;
|
||||
|
||||
use super::state::{WindowCompositorUpdate, WinitState};
|
||||
use super::window::state::FrameCallbackState;
|
||||
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
|
||||
use super::{DeviceId, WaylandError, WindowId};
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
|
||||
@@ -355,13 +356,15 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
for mut compositor_update in compositor_updates.drain(..) {
|
||||
let window_id = compositor_update.window_id;
|
||||
if compositor_update.scale_changed {
|
||||
let (physical_size, scale_factor) = self.with_state(|state| {
|
||||
if let Some(scale_factor) = compositor_update.scale_factor {
|
||||
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 size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
(size, scale_factor)
|
||||
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)
|
||||
});
|
||||
|
||||
// Stash the old window size.
|
||||
@@ -383,32 +386,30 @@ 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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.size = Some(new_logical_size);
|
||||
}
|
||||
|
||||
// NOTE: Rescale changed the physical size which winit operates in, thus we should
|
||||
// resize.
|
||||
if compositor_update.resized || compositor_update.scale_changed {
|
||||
if let Some(size) = compositor_update.size.take() {
|
||||
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 size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
|
||||
// TODO could probably bring back size reporting optimization.
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
state
|
||||
@@ -419,7 +420,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
size
|
||||
physical_size
|
||||
});
|
||||
|
||||
callback(
|
||||
@@ -465,45 +466,45 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_ids.extend(state.window_requests.get_mut().keys());
|
||||
});
|
||||
|
||||
for window_id in window_ids.iter() {
|
||||
let event = self.with_state(|state| {
|
||||
for window_id in window_ids.drain(..) {
|
||||
let request_redraw = 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));
|
||||
return Some(WindowEvent::Destroyed);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 let Some(event) = event {
|
||||
if request_redraw {
|
||||
callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event,
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
},
|
||||
&self.window_target,
|
||||
);
|
||||
@@ -518,42 +519,6 @@ 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);
|
||||
@@ -664,34 +629,6 @@ 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) {}
|
||||
|
||||
@@ -719,3 +656,10 @@ 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,7 +9,6 @@ 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};
|
||||
@@ -77,10 +76,3 @@ 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,6 +4,7 @@ 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;
|
||||
@@ -23,6 +24,30 @@ 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::Context;
|
||||
use crate::platform_impl::common::xkb_state::KbdState;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::seat::WinitSeatState;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
@@ -43,10 +43,14 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
WlKeymapFormat::NoKeymap => {
|
||||
warn!("non-xkb compatible keymap")
|
||||
}
|
||||
WlKeymapFormat::XkbV1 => {
|
||||
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
context.set_keymap_from_fd(fd, size as usize);
|
||||
}
|
||||
WlKeymapFormat::XkbV1 => unsafe {
|
||||
seat_state
|
||||
.keyboard_state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.xkb_state
|
||||
.init_with_fd(fd, size as usize);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
WEnum::Unknown(value) => {
|
||||
@@ -57,13 +61,8 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// Mark the window as focused.
|
||||
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
|
||||
}
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(true),
|
||||
None => return,
|
||||
};
|
||||
|
||||
@@ -74,14 +73,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
*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);
|
||||
}
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
|
||||
*data.window_id.lock().unwrap() = Some(window_id);
|
||||
|
||||
// HACK: this is just for GNOME not fixing their ordering issue of modifiers.
|
||||
if std::mem::take(&mut seat_state.modifiers_pending) {
|
||||
@@ -104,30 +101,24 @@ 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.
|
||||
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()
|
||||
}
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(false),
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
}
|
||||
WlKeyboardEvent::Key {
|
||||
key,
|
||||
@@ -151,12 +142,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
RepeatInfo::Disable => return,
|
||||
};
|
||||
|
||||
if !keyboard_state
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
{
|
||||
if !keyboard_state.xkb_state.key_repeats(key) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -222,11 +208,7 @@ 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_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
&& keyboard_state.xkb_state.key_repeats(key)
|
||||
&& Some(key) == keyboard_state.current_repeat
|
||||
{
|
||||
keyboard_state.current_repeat = None;
|
||||
@@ -242,14 +224,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
group,
|
||||
..
|
||||
} => {
|
||||
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,
|
||||
};
|
||||
|
||||
let xkb_state = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_state;
|
||||
xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
seat_state.modifiers = xkb_state.modifiers().into();
|
||||
seat_state.modifiers = xkb_state.mods_state().into();
|
||||
|
||||
// HACK: part of the workaround from `WlKeyboardEvent::Enter`.
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
@@ -295,7 +272,7 @@ pub struct KeyboardState {
|
||||
pub loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// The state of the keyboard.
|
||||
pub xkb_context: Context,
|
||||
pub xkb_state: KbdState,
|
||||
|
||||
/// The information about the repeat rate obtained from the compositor.
|
||||
pub repeat_info: RepeatInfo,
|
||||
@@ -312,7 +289,7 @@ impl KeyboardState {
|
||||
Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
xkb_context: Context::new().unwrap(),
|
||||
xkb_state: KbdState::new().unwrap(),
|
||||
repeat_info: RepeatInfo::default(),
|
||||
repeat_token: None,
|
||||
current_repeat: None,
|
||||
@@ -395,13 +372,16 @@ 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));
|
||||
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 {
|
||||
let event = keyboard_state
|
||||
.xkb_state
|
||||
.process_key_event(keycode, state, repeat);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
};
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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};
|
||||
@@ -14,7 +13,6 @@ 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;
|
||||
|
||||
@@ -145,10 +143,6 @@ 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() {
|
||||
@@ -180,10 +174,13 @@ 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(
|
||||
@@ -202,21 +199,6 @@ 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,34 +60,19 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,22 +19,25 @@ use sctk::seat::SeatState;
|
||||
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
|
||||
use sctk::shell::xdg::XdgShell;
|
||||
use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::slot::SlotPool;
|
||||
use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::output::MonitorHandle;
|
||||
use crate::platform_impl::wayland::seat::{
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
use super::seat::{
|
||||
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
|
||||
WinitPointerDataExt, WinitSeatState,
|
||||
};
|
||||
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;
|
||||
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};
|
||||
|
||||
/// Winit's Wayland state.
|
||||
pub struct WinitState {
|
||||
@@ -56,6 +59,9 @@ pub struct WinitState {
|
||||
/// The shm for software buffers, such as cursors.
|
||||
pub shm: Shm,
|
||||
|
||||
/// The pool where custom cursors are allocated.
|
||||
pub custom_cursor_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// The XDG shell that is used for widnows.
|
||||
pub xdg_shell: XdgShell,
|
||||
|
||||
@@ -151,13 +157,17 @@ impl WinitState {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
|
||||
|
||||
Ok(Self {
|
||||
registry_state,
|
||||
compositor_state: Arc::new(compositor_state),
|
||||
subcompositor_state: subcompositor_state.map(Arc::new),
|
||||
output_state,
|
||||
seat_state,
|
||||
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
shm,
|
||||
custom_cursor_pool,
|
||||
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
|
||||
@@ -217,7 +227,7 @@ impl WinitState {
|
||||
|
||||
// Update the scale factor right away.
|
||||
window.lock().unwrap().set_scale_factor(scale_factor);
|
||||
self.window_compositor_updates[pos].scale_changed = true;
|
||||
self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
|
||||
} 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() {
|
||||
@@ -281,26 +291,23 @@ impl WindowHandler for WinitState {
|
||||
};
|
||||
|
||||
// Populate the configure to the window.
|
||||
self.window_compositor_updates[pos].resized |= self
|
||||
//
|
||||
// XXX the size on the window will be updated right before dispatching the size to the user.
|
||||
let new_size = self
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.expect("got configure for dead window.")
|
||||
.lock()
|
||||
.unwrap()
|
||||
.configure(configure, &self.shm, &self.subcompositor_state);
|
||||
.configure(
|
||||
configure,
|
||||
&self.shm,
|
||||
&self.subcompositor_state,
|
||||
&mut self.events_sink,
|
||||
);
|
||||
|
||||
// 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;
|
||||
self.window_compositor_updates[pos].size = Some(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,10 +401,10 @@ pub struct WindowCompositorUpdate {
|
||||
pub window_id: WindowId,
|
||||
|
||||
/// New window size.
|
||||
pub resized: bool,
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_changed: bool,
|
||||
pub scale_factor: Option<f64>,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
@@ -407,8 +414,8 @@ impl WindowCompositorUpdate {
|
||||
fn new(window_id: WindowId) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
resized: false,
|
||||
scale_changed: false,
|
||||
size: None,
|
||||
scale_factor: None,
|
||||
close_window: false,
|
||||
}
|
||||
}
|
||||
|
||||
56
src/platform_impl/linux/wayland/types/cursor.rs
Normal file
56
src/platform_impl/linux/wayland/types/cursor.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use cursor_icon::CursorIcon;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_shm::Format;
|
||||
use sctk::shm::slot::{Buffer, SlotPool};
|
||||
|
||||
use crate::cursor::CursorImage;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SelectedCursor {
|
||||
Named(CursorIcon),
|
||||
Custom(CustomCursor),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
Self::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomCursor {
|
||||
pub buffer: Buffer,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
pub hotspot_x: i32,
|
||||
pub hotspot_y: i32,
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
pub fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
|
||||
let (buffer, canvas) = pool
|
||||
.create_buffer(
|
||||
image.width as i32,
|
||||
image.height as i32,
|
||||
4 * (image.width as i32),
|
||||
Format::Argb8888,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (canvas_chunk, rgba_chunk) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4))
|
||||
{
|
||||
canvas_chunk[0] = rgba_chunk[2];
|
||||
canvas_chunk[1] = rgba_chunk[1];
|
||||
canvas_chunk[2] = rgba_chunk[0];
|
||||
canvas_chunk[3] = rgba_chunk[3];
|
||||
}
|
||||
|
||||
CustomCursor {
|
||||
buffer,
|
||||
w: image.width as i32,
|
||||
h: image.height as i32,
|
||||
hotspot_x: image.hotspot_x as i32,
|
||||
hotspot_y: image.hotspot_y as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Wayland protocol implementation boilerplate.
|
||||
|
||||
pub mod cursor;
|
||||
pub mod kwin_blur;
|
||||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_viewporter;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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;
|
||||
@@ -14,6 +15,7 @@ use sctk::shell::xdg::window::Window as SctkWindow;
|
||||
use sctk::shell::xdg::window::WindowDecorations;
|
||||
use sctk::shell::WaylandSurface;
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
@@ -279,7 +281,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();
|
||||
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
|
||||
window_state.inner_size().to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -307,7 +309,7 @@ impl Window {
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
|
||||
window_state.outer_size().to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -326,9 +328,7 @@ impl Window {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_min_inner_size(min_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
.set_min_inner_size(min_size)
|
||||
}
|
||||
|
||||
/// Set the maximum inner size for the window.
|
||||
@@ -339,9 +339,7 @@ impl Window {
|
||||
self.window_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_max_inner_size(max_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
.set_max_inner_size(max_size)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -390,10 +388,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
if self.window_state.lock().unwrap().set_resizable(resizable) {
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
self.window_state.lock().unwrap().set_resizable(resizable);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -512,6 +507,11 @@ impl Window {
|
||||
self.window_state.lock().unwrap().set_cursor(cursor);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
self.window_state.lock().unwrap().set_custom_cursor(cursor);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.window_state
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
//! The state of the window, which is shared with the event-loop.
|
||||
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::{Arc, Mutex, 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;
|
||||
@@ -20,18 +18,23 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
|
||||
|
||||
use sctk::compositor::{CompositorState, Region};
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
|
||||
use sctk::seat::pointer::{PointerDataExt, ThemedPointer};
|
||||
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
|
||||
use sctk::shell::xdg::XdgSurface;
|
||||
use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::slot::SlotPool;
|
||||
use sctk::shm::Shm;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::cursor::CustomCursor as RootCustomCursor;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::platform_impl::wayland::logical_to_physical_rounded;
|
||||
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::types::cursor::{CustomCursor, SelectedCursor};
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
|
||||
@@ -60,14 +63,16 @@ pub struct WindowState {
|
||||
/// The `Shm` to set cursor.
|
||||
pub shm: WlShm,
|
||||
|
||||
// A shared pool where to allocate custom cursors.
|
||||
custom_cursor_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// The last received configure.
|
||||
pub last_configure: Option<WindowConfigure>,
|
||||
|
||||
/// The pointers observed on the window.
|
||||
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
/// Cursor icon.
|
||||
pub cursor_icon: CursorIcon,
|
||||
selected_cursor: SelectedCursor,
|
||||
|
||||
/// Wether the cursor is visible.
|
||||
pub cursor_visible: bool,
|
||||
@@ -87,10 +92,8 @@ pub struct WindowState {
|
||||
/// Whether the frame is resizable.
|
||||
resizable: 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>,
|
||||
/// Whether the window has focus.
|
||||
has_focus: bool,
|
||||
|
||||
/// The scale factor of the window.
|
||||
scale_factor: f64,
|
||||
@@ -180,13 +183,13 @@ impl WindowState {
|
||||
connection,
|
||||
csd_fails: false,
|
||||
cursor_grab_mode: GrabState::new(),
|
||||
cursor_icon: CursorIcon::Default,
|
||||
selected_cursor: Default::default(),
|
||||
cursor_visible: true,
|
||||
decorate: true,
|
||||
fractional_scale,
|
||||
frame: None,
|
||||
frame_callback_state: FrameCallbackState::None,
|
||||
seat_focus: Default::default(),
|
||||
has_focus: false,
|
||||
has_pending_move: None,
|
||||
ime_allowed: false,
|
||||
ime_purpose: ImePurpose::Normal,
|
||||
@@ -199,6 +202,7 @@ impl WindowState {
|
||||
resizable: true,
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
|
||||
size: initial_size.to_logical(1.),
|
||||
stateless_size: initial_size.to_logical(1.),
|
||||
initial_size: Some(initial_size),
|
||||
@@ -257,7 +261,8 @@ impl WindowState {
|
||||
configure: WindowConfigure,
|
||||
shm: &Shm,
|
||||
subcompositor: &Option<Arc<SubcompositorState>>,
|
||||
) -> bool {
|
||||
event_sink: &mut EventSink,
|
||||
) -> LogicalSize<u32> {
|
||||
// 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.
|
||||
@@ -300,6 +305,19 @@ 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 (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
|
||||
// Configure the window states.
|
||||
frame.update_state(configure.state);
|
||||
@@ -307,9 +325,14 @@ impl WindowState {
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => {
|
||||
let (width, height) = frame.subtract_borders(width, height);
|
||||
let width = width.map(|w| w.get()).unwrap_or(1);
|
||||
let height = height.map(|h| h.get()).unwrap_or(1);
|
||||
((width, height).into(), false)
|
||||
(
|
||||
(
|
||||
width.map(|w| w.get()).unwrap_or(1),
|
||||
height.map(|h| h.get()).unwrap_or(1),
|
||||
)
|
||||
.into(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
(_, _) if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
@@ -335,31 +358,13 @@ impl WindowState {
|
||||
.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.
|
||||
// XXX Set the configure before doing a resize.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
if state_change_requires_resize || new_size != self.inner_size() {
|
||||
self.resize(new_size);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
// XXX Update the new size right away.
|
||||
self.resize(new_size);
|
||||
|
||||
new_size
|
||||
}
|
||||
|
||||
/// Compute the bounds for the inner size of the surface.
|
||||
@@ -498,12 +503,10 @@ 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) -> bool {
|
||||
pub fn set_resizable(&mut self, resizable: bool) {
|
||||
if self.resizable == resizable {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
self.resizable = resizable;
|
||||
@@ -519,14 +522,12 @@ impl WindowState {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_resizable(resizable);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether the window is focused by any seat.
|
||||
/// Whether the window is focused.
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
!self.seat_focus.is_empty()
|
||||
self.has_focus
|
||||
}
|
||||
|
||||
/// Whether the IME is allowed.
|
||||
@@ -608,7 +609,10 @@ impl WindowState {
|
||||
/// Reload the cursor style on the given window.
|
||||
pub fn reload_cursor_style(&mut self) {
|
||||
if self.cursor_visible {
|
||||
self.set_cursor(self.cursor_icon);
|
||||
match &self.selected_cursor {
|
||||
SelectedCursor::Named(icon) => self.set_cursor(*icon),
|
||||
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
|
||||
}
|
||||
} else {
|
||||
self.set_cursor_visible(self.cursor_visible);
|
||||
}
|
||||
@@ -639,7 +643,7 @@ impl WindowState {
|
||||
self.resize(inner_size.to_logical(self.scale_factor()))
|
||||
}
|
||||
|
||||
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
|
||||
self.inner_size().to_physical(self.scale_factor())
|
||||
}
|
||||
|
||||
/// Resize the window to the new inner size.
|
||||
@@ -694,10 +698,8 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Set the cursor icon.
|
||||
///
|
||||
/// Providing `None` will hide the cursor.
|
||||
pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
|
||||
self.cursor_icon = cursor_icon;
|
||||
self.selected_cursor = SelectedCursor::Named(cursor_icon);
|
||||
|
||||
if !self.cursor_visible {
|
||||
return;
|
||||
@@ -710,6 +712,54 @@ impl WindowState {
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the custom cursor icon.
|
||||
pub fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
|
||||
let cursor = {
|
||||
let mut pool = self.custom_cursor_pool.lock().unwrap();
|
||||
CustomCursor::new(&mut pool, &cursor.inner)
|
||||
};
|
||||
|
||||
if self.cursor_visible {
|
||||
self.apply_custom_cursor(&cursor);
|
||||
}
|
||||
|
||||
self.selected_cursor = SelectedCursor::Custom(cursor);
|
||||
}
|
||||
|
||||
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
|
||||
self.apply_on_poiner(|pointer, _| {
|
||||
let surface = pointer.surface();
|
||||
|
||||
let scale = surface
|
||||
.data::<SurfaceData>()
|
||||
.unwrap()
|
||||
.surface_data()
|
||||
.scale_factor();
|
||||
|
||||
surface.set_buffer_scale(scale);
|
||||
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
|
||||
if surface.version() >= 4 {
|
||||
surface.damage_buffer(0, 0, cursor.w, cursor.h);
|
||||
} else {
|
||||
surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
|
||||
}
|
||||
surface.commit();
|
||||
|
||||
let serial = pointer
|
||||
.pointer()
|
||||
.data::<WinitPointerData>()
|
||||
.and_then(|data| data.pointer_data().latest_enter_serial())
|
||||
.unwrap();
|
||||
|
||||
pointer.pointer().set_cursor(
|
||||
serial,
|
||||
Some(surface),
|
||||
cursor.hotspot_x / scale,
|
||||
cursor.hotspot_y / scale,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Set maximum inner window size.
|
||||
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
// Ensure that the window has the right minimum size.
|
||||
@@ -758,14 +808,9 @@ impl WindowState {
|
||||
|
||||
/// Set the cursor grabbing state on the top-level.
|
||||
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
if self.cursor_grab_mode.user_grab_mode == mode {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.set_cursor_grab_inner(mode)?;
|
||||
// Update user grab on success.
|
||||
// Replace the user grabbing mode.
|
||||
self.cursor_grab_mode.user_grab_mode = mode;
|
||||
Ok(())
|
||||
self.set_cursor_grab_inner(mode)
|
||||
}
|
||||
|
||||
/// Reload the hints for minimum and maximum sizes.
|
||||
@@ -849,7 +894,10 @@ impl WindowState {
|
||||
self.cursor_visible = cursor_visible;
|
||||
|
||||
if self.cursor_visible {
|
||||
self.set_cursor(self.cursor_icon);
|
||||
match &self.selected_cursor {
|
||||
SelectedCursor::Named(icon) => self.set_cursor(*icon),
|
||||
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
|
||||
}
|
||||
} else {
|
||||
for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
|
||||
let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
|
||||
@@ -893,16 +941,12 @@ impl WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add seat focus for the window.
|
||||
/// Mark that the window has focus.
|
||||
///
|
||||
/// Should be used from routine that sends focused event.
|
||||
#[inline]
|
||||
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);
|
||||
pub fn set_has_focus(&mut self, has_focus: bool) {
|
||||
self.has_focus = has_focus;
|
||||
}
|
||||
|
||||
/// Returns `true` if the requested state was applied.
|
||||
@@ -926,7 +970,7 @@ impl WindowState {
|
||||
|
||||
/// Set the IME position.
|
||||
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
|
||||
// FIXME: This won't fly unless user will have a way to request IME window per seat, since
|
||||
// XXX 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);
|
||||
@@ -957,7 +1001,7 @@ impl WindowState {
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f64) {
|
||||
self.scale_factor = scale_factor;
|
||||
|
||||
// NOTE: When fractional scaling is not used update the buffer scale.
|
||||
// XXX 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 _);
|
||||
}
|
||||
@@ -1105,7 +1149,7 @@ impl From<ResizeDirection> for XdgResizeEdge {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Rust doesn't allow `From<Option<Theme>>`.
|
||||
// XXX rust doesn't allow from `Option`.
|
||||
#[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 Atoms: AtomsCookie {
|
||||
pub(crate) 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 enum AtomName {
|
||||
pub(crate) enum AtomName {
|
||||
$($name,)*
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ atom_manager! {
|
||||
WM_DELETE_WINDOW,
|
||||
WM_PROTOCOLS,
|
||||
WM_STATE,
|
||||
XIM_SERVERS,
|
||||
|
||||
// Assorted ICCCM Atoms
|
||||
_NET_WM_ICON,
|
||||
@@ -100,8 +99,7 @@ atom_manager! {
|
||||
_NET_FRAME_EXTENTS,
|
||||
_NET_SUPPORTED,
|
||||
_NET_SUPPORTING_WM_CHECK,
|
||||
_XEMBED,
|
||||
_XSETTINGS_SETTINGS
|
||||
_XEMBED
|
||||
}
|
||||
|
||||
impl Index<AtomName> for Atoms {
|
||||
|
||||
@@ -41,10 +41,10 @@ impl From<io::Error> for DndDataParseError {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dnd {
|
||||
pub(crate) struct Dnd {
|
||||
xconn: Arc<XConnection>,
|
||||
// Populated by XdndEnter event handler
|
||||
pub version: Option<c_long>,
|
||||
pub version: Option<u32>,
|
||||
pub type_list: Option<Vec<xproto::Atom>>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub source_window: Option<xproto::Window>,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
862
src/platform_impl/linux/x11/ime.rs
Normal file
862
src/platform_impl/linux/x11/ime.rs
Normal file
@@ -0,0 +1,862 @@
|
||||
//! IME handler, using the xim-rs crate.
|
||||
|
||||
use super::{X11Error, X11rbConnection, XConnection};
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::xproto::Window;
|
||||
use x11rb::protocol::Event;
|
||||
|
||||
use xim::x11rb::{HasConnection, X11rbClient};
|
||||
use xim::{AttributeName, Client as _, ClientError, ClientHandler, InputStyle, Point};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl HasConnection for XConnection {
|
||||
type Connection = X11rbConnection;
|
||||
|
||||
fn conn(&self) -> &Self::Connection {
|
||||
self.xcb_connection()
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of the IME events that can occur.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ImeEvent {
|
||||
Enabled,
|
||||
Start,
|
||||
Update(String, Option<usize>),
|
||||
Commit(String),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Invalid states that an IME client can enter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum InvalidImeState {
|
||||
/// The IME has no style information.
|
||||
NoStyle,
|
||||
|
||||
/// No windows in the pending window queue.
|
||||
NoWindows,
|
||||
|
||||
/// Invalid input context.
|
||||
InvalidIc(u16),
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidImeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InvalidImeState::NoStyle => write!(f, "IME has no style information"),
|
||||
InvalidImeState::NoWindows => {
|
||||
write!(f, "IME has no windows in the pending window queue")
|
||||
}
|
||||
InvalidImeState::InvalidIc(ic) => write!(f, "IME has invalid input context {}", ic),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request to control XIM handler from the window.
|
||||
pub enum ImeRequest {
|
||||
/// Set IME spot position for given `window_id`.
|
||||
Position(Window, i16, i16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(Window, bool),
|
||||
}
|
||||
|
||||
/// The IME data for winit.
|
||||
pub(super) struct ImeData {
|
||||
/// The XIM client manager.
|
||||
client: X11rbClient<Arc<XConnection>>,
|
||||
|
||||
/// Relevant IME data.
|
||||
handler: ImeHandler,
|
||||
}
|
||||
|
||||
/// Inner IME handler.
|
||||
struct ImeHandler {
|
||||
/// Handle to the event queue.
|
||||
event_queue: Rc<RefCell<VecDeque<Event>>>,
|
||||
|
||||
/// Whether IME is currently disconnected.
|
||||
disconnected: bool,
|
||||
|
||||
/// IME events waiting to be read.
|
||||
ime_events: VecDeque<(Window, ImeEvent)>,
|
||||
|
||||
/// Windows waiting to be assigned an input context.
|
||||
pending_windows: VecDeque<WindowData>,
|
||||
|
||||
/// Currently registered input styles.
|
||||
styles: Option<(Style, Style)>,
|
||||
|
||||
/// The input method for the display, if there is one.
|
||||
input_method: Option<u16>,
|
||||
|
||||
/// Hash map between input contexts and their associated data.
|
||||
input_contexts: HashMap<u16, IcData>,
|
||||
|
||||
/// Map between window IDs and their associated input contexts.
|
||||
window_contexts: HashMap<Window, u16>,
|
||||
}
|
||||
|
||||
/// Data relevant for each input context.
|
||||
struct IcData {
|
||||
/// Data associated with the window.
|
||||
window: WindowData,
|
||||
|
||||
/// Newly set point for the context.
|
||||
new_spot: Option<Point>,
|
||||
|
||||
/// The current preedit string.
|
||||
///
|
||||
/// We use a `Vec<char>` here instead of a string because the IME indices operate on chars,
|
||||
/// not bytes.
|
||||
text: Vec<char>,
|
||||
|
||||
/// The current cursor position in the preedit string.
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
/// Windows waiting for IME events.
|
||||
struct WindowData {
|
||||
/// The window ID.
|
||||
id: Window,
|
||||
|
||||
/// The style of the window.
|
||||
style: Style,
|
||||
|
||||
/// Current "spot" for the context.
|
||||
spot: Point,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Style {
|
||||
Preedit,
|
||||
Nothing,
|
||||
None,
|
||||
}
|
||||
|
||||
impl ImeData {
|
||||
/// Creates the IME data for the display.
|
||||
pub(super) fn new(
|
||||
conn: &Arc<XConnection>,
|
||||
screen: usize,
|
||||
event_queue: &Rc<RefCell<VecDeque<Event>>>,
|
||||
) -> Result<Self, X11Error> {
|
||||
// IM servers to try, in order:
|
||||
// - None, which defaults to the environment variable `XMODIFIERS` in xim's impl.
|
||||
// - "local", which is the default for most IMEs.
|
||||
// - empty string, which may work in some cases.
|
||||
let input_methods = [None, Some("local"), Some("")];
|
||||
let mut last_error = X11Error::Ime(ClientError::NoXimServer);
|
||||
|
||||
for im in input_methods {
|
||||
// Try to initialize a client here.
|
||||
match X11rbClient::init(conn.clone(), screen, im) {
|
||||
Ok(client) => {
|
||||
return Ok(Self {
|
||||
client,
|
||||
handler: ImeHandler {
|
||||
event_queue: event_queue.clone(),
|
||||
disconnected: true,
|
||||
ime_events: VecDeque::new(),
|
||||
pending_windows: VecDeque::new(),
|
||||
styles: None,
|
||||
input_method: None,
|
||||
input_contexts: HashMap::new(),
|
||||
window_contexts: HashMap::new(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
log::warn!("Failed to create XIM client for {:?}: {err}", ImData(im));
|
||||
last_error = X11Error::Ime(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error)
|
||||
}
|
||||
|
||||
/// Filter an event.
|
||||
pub(super) fn filter_event(&mut self, event: &Event) -> Result<bool, X11Error> {
|
||||
self.client
|
||||
.filter_event(event, &mut self.handler)
|
||||
.map_err(X11Error::Ime)
|
||||
}
|
||||
|
||||
/// Connection to the X server.
|
||||
fn conn(&self) -> &X11rbConnection {
|
||||
self.client.conn()
|
||||
}
|
||||
|
||||
/// Get an IME event.
|
||||
pub(super) fn next_ime_event(&mut self) -> Option<(Window, ImeEvent)> {
|
||||
self.handler.ime_events.pop_front()
|
||||
}
|
||||
|
||||
/// Create a new IME context for the provided window.
|
||||
pub(super) fn create_context(
|
||||
&mut self,
|
||||
window: Window,
|
||||
with_preedit: bool,
|
||||
spot: Option<Point>,
|
||||
) -> Result<bool, X11Error> {
|
||||
// If we aren't connected, nothing can be done.
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
let method = match self.handler.input_method {
|
||||
Some(im) => im,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// Get the current style.
|
||||
let style = match (self.handler.styles, with_preedit) {
|
||||
(None, _) => return Err(X11Error::InvalidImeState(InvalidImeState::NoStyle)),
|
||||
(Some((preedit_style, _)), true) => preedit_style,
|
||||
(Some((_, none_style)), false) => none_style,
|
||||
};
|
||||
|
||||
// Setup IC attributes.
|
||||
let ic_attributes = {
|
||||
let mut ic_attributes = self
|
||||
.client
|
||||
.build_ic_attributes()
|
||||
.push(AttributeName::ClientWindow, window);
|
||||
|
||||
let ic_style = match style {
|
||||
Style::Preedit => InputStyle::PREEDIT_POSITION | InputStyle::STATUS_NOTHING,
|
||||
Style::Nothing => InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING,
|
||||
Style::None => InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE,
|
||||
};
|
||||
|
||||
if let Some(spot) = spot.clone() {
|
||||
ic_attributes = ic_attributes.push(AttributeName::SpotLocation, spot);
|
||||
}
|
||||
|
||||
ic_attributes
|
||||
.push(AttributeName::InputStyle, ic_style)
|
||||
.build()
|
||||
};
|
||||
|
||||
// Create the IC.
|
||||
self.client.create_ic(method, ic_attributes)?;
|
||||
|
||||
// Add to the waiting window list.
|
||||
self.handler.pending_windows.push_back(WindowData {
|
||||
id: window,
|
||||
style,
|
||||
spot: spot.unwrap_or(Point { x: 0, y: 0 }),
|
||||
});
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Remove an IME context for a window.
|
||||
pub(super) fn remove_context(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
let method = match self.handler.input_method {
|
||||
Some(im) => im,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// Remove the pending window if it's still pending.
|
||||
let mut removed = false;
|
||||
self.handler.pending_windows.retain(|pending| {
|
||||
if pending.id == window {
|
||||
removed = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if removed {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Remove the IC if it's already created.
|
||||
if let Some(ic) = self.handler.window_contexts.remove(&window) {
|
||||
self.handler.input_contexts.remove(&ic);
|
||||
|
||||
// Destroy the IC.
|
||||
self.client.destroy_ic(method, ic)?;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Focus an IME context.
|
||||
pub(super) fn focus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
self.client.set_focus(method, ic)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Unfocus an IME context.
|
||||
pub(super) fn unfocus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
self.client.unset_focus(method, ic)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Set the spot for an IME context.
|
||||
pub(super) fn set_spot(&mut self, window: Window, x: i16, y: i16) -> Result<(), X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
// If the IC is not available, or if the spot is the same, then we don't need to update.
|
||||
let ic_data = match self.handler.input_contexts.get_mut(&ic) {
|
||||
Some(ic_data) => ic_data,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let new_point = Point { x, y };
|
||||
if !matches!(ic_data.window.style, Style::None) || ic_data.window.spot == new_point {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let new_attrs = self
|
||||
.client
|
||||
.build_ic_attributes()
|
||||
.push(AttributeName::SpotLocation, new_point.clone())
|
||||
.build();
|
||||
self.client.set_ic_values(method, ic, new_attrs)?;
|
||||
|
||||
// Indicate that we have a new spot.
|
||||
debug_assert!(ic_data.new_spot.is_none());
|
||||
ic_data.new_spot = Some(new_point);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn set_ime_allowed(
|
||||
&mut self,
|
||||
window: Window,
|
||||
allowed: bool,
|
||||
) -> Result<(), X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Get the client info.
|
||||
let _ = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
let mut spot = None;
|
||||
|
||||
// See if we need to update the allowed state.
|
||||
if let Some(ic_data) = self.handler.input_contexts.get(&ic) {
|
||||
spot = Some(ic_data.window.spot.clone());
|
||||
if matches!(ic_data.window.style, Style::None) != allowed {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete and re-install the IC.
|
||||
self.remove_context(window)?;
|
||||
self.create_context(window, allowed, spot)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for the input method to be set.
|
||||
fn wait_for_method(&mut self) -> Result<u16, X11Error> {
|
||||
loop {
|
||||
if let Some(im) = self.handler.input_method {
|
||||
return Ok(im);
|
||||
}
|
||||
|
||||
// Wait and hope the input method is set.
|
||||
self.block_for_ime()?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for an input context to be set.
|
||||
fn wait_for_context(&mut self, window: Window) -> Result<Option<u16>, X11Error> {
|
||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
||||
return Ok(Some(*cid));
|
||||
}
|
||||
|
||||
// If the window isn't in our pending windows queue, there's no way for it to get an IC.
|
||||
if !self
|
||||
.handler
|
||||
.pending_windows
|
||||
.iter()
|
||||
.any(|WindowData { id, .. }| *id == window)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
loop {
|
||||
self.block_for_ime()?;
|
||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
||||
return Ok(Some(*cid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until we've acted on an IME event.
|
||||
fn block_for_ime(&mut self) -> Result<(), X11Error> {
|
||||
let mut last_event = self.conn().poll_for_event()?;
|
||||
|
||||
loop {
|
||||
if let Some(last_event) = last_event.as_ref() {
|
||||
if self.filter_event(last_event)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// This scope keeps track of the event queue handle.
|
||||
{
|
||||
// Check the event queue for events.
|
||||
let event_queue = self.handler.event_queue.clone();
|
||||
let mut event_queue = event_queue.borrow_mut();
|
||||
|
||||
// Check the event queue for events we can use.
|
||||
let mut found_event = false;
|
||||
let mut last_err = None;
|
||||
event_queue.retain(|event| match self.filter_event(event) {
|
||||
Ok(false) => {
|
||||
found_event = true;
|
||||
true
|
||||
}
|
||||
Ok(true) => false,
|
||||
Err(err) => {
|
||||
last_err = Some(err);
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
// Push our own event to the queue.
|
||||
if let Some(last_event) = last_event.take() {
|
||||
event_queue.push_back(last_event);
|
||||
}
|
||||
|
||||
// Check for errors.
|
||||
if let Some(err) = last_err {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// If we found an event, then we're done.
|
||||
if found_event {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Waiting for IME event");
|
||||
last_event = Some(self.conn().wait_for_event()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: xim::Client> ClientHandler<C> for ImeHandler {
|
||||
fn handle_connect(&mut self, client: &mut C) -> Result<(), ClientError> {
|
||||
// We have been connected, now request a new input method for our current locale.
|
||||
self.disconnected = false;
|
||||
client.open(&locale())
|
||||
}
|
||||
|
||||
fn handle_disconnect(&mut self) {
|
||||
// We are now disconnected.
|
||||
self.disconnected = true;
|
||||
}
|
||||
|
||||
fn handle_open(&mut self, client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
||||
// We now have an input method.
|
||||
debug_assert!(self.input_method.is_none());
|
||||
self.input_method = Some(input_method_id);
|
||||
|
||||
// Ask for the IM's attributes.
|
||||
client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle])
|
||||
}
|
||||
|
||||
fn handle_close(&mut self, _client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
||||
// No more input method.
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
self.input_method = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_im_values(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
mut attributes: xim::AHashMap<xim::AttributeName, Vec<u8>>,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the input styles.
|
||||
let mut preedit_style = None;
|
||||
let mut none_style = None;
|
||||
|
||||
let styles = {
|
||||
let style = attributes
|
||||
.remove(&AttributeName::QueryInputStyle)
|
||||
.expect("No query input style");
|
||||
let mut result = vec![0u32; style.len() / 4];
|
||||
|
||||
bytemuck::cast_slice_mut::<u32, u8>(&mut result).copy_from_slice(&style);
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
{
|
||||
// The styles that we're looking for.
|
||||
let lu_preedit_style = InputStyle::PREEDIT_CALLBACKS | InputStyle::STATUS_NOTHING;
|
||||
let lu_nothing_style = InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING;
|
||||
let lu_none_style = InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE;
|
||||
|
||||
for style in styles {
|
||||
let style = InputStyle::from_bits_truncate(style);
|
||||
|
||||
if style == lu_preedit_style {
|
||||
preedit_style = Some(Style::Preedit);
|
||||
} else if style == lu_nothing_style {
|
||||
preedit_style = Some(Style::Nothing);
|
||||
} else if style == lu_none_style {
|
||||
none_style = Some(Style::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (preedit_style, none_style) = match (preedit_style, none_style) {
|
||||
(None, None) => {
|
||||
log::error!("No supported input styles found");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
(Some(style), None) | (None, Some(style)) => (style, style),
|
||||
|
||||
(Some(preedit_style), Some(none_style)) => (preedit_style, none_style),
|
||||
};
|
||||
|
||||
self.styles = Some((preedit_style, none_style));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_create_ic(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the window that wanted the IC context.
|
||||
let window = self
|
||||
.pending_windows
|
||||
.pop_front()
|
||||
.ok_or_else(|| invalid_state(InvalidImeState::NoWindows))?;
|
||||
|
||||
// Create the IC data.
|
||||
let ic_data = IcData {
|
||||
window,
|
||||
new_spot: None,
|
||||
text: Vec::new(),
|
||||
cursor: 0,
|
||||
};
|
||||
|
||||
// Store the context.
|
||||
let (window, style) = (ic_data.window.id, ic_data.window.style);
|
||||
self.input_contexts.insert(input_context_id, ic_data);
|
||||
self.window_contexts.insert(window, input_context_id);
|
||||
|
||||
// Indicate our status.
|
||||
let event = if matches!(style, Style::Nothing) {
|
||||
ImeEvent::Disabled
|
||||
} else {
|
||||
ImeEvent::Enabled
|
||||
};
|
||||
self.ime_events.push_back((window, event));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_destroy_ic(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
// This is already handled by the higher-level function.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_ic_values(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the IC data.
|
||||
let ic_data = self
|
||||
.input_contexts
|
||||
.get_mut(&input_context_id)
|
||||
.ok_or_else(|| invalid_state(InvalidImeState::InvalidIc(input_context_id)))?;
|
||||
|
||||
// Move up the new spot
|
||||
if let Some(spot) = ic_data.new_spot.take() {
|
||||
ic_data.window.spot = spot;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_start(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Start a pre-edit.
|
||||
ic_data.text.clear();
|
||||
ic_data.cursor = 0;
|
||||
|
||||
// Indicate the start.
|
||||
self.ime_events
|
||||
.push_back((ic_data.window.id, ImeEvent::Start));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_draw(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
caret: i32,
|
||||
chg_first: i32,
|
||||
chg_len: i32,
|
||||
_status: xim::PreeditDrawStatus,
|
||||
preedit_string: &str,
|
||||
_feedbacks: Vec<xim::Feedback>,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Set the cursor.
|
||||
ic_data.cursor = caret as usize;
|
||||
|
||||
// Figure out the range of text to change.
|
||||
let change_range = chg_first as usize..(chg_first + chg_len) as usize;
|
||||
|
||||
// If the range doesn't fit our current text, warn and return.
|
||||
if change_range.start > ic_data.text.len() || change_range.end > ic_data.text.len() {
|
||||
warn!(
|
||||
"Preedit draw range {}..{} doesn't fit text of length {}",
|
||||
change_range.start,
|
||||
change_range.end,
|
||||
ic_data.text.len()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Update the text in the changed range.
|
||||
{
|
||||
let text = &mut ic_data.text;
|
||||
let mut old_text_tail = text.split_off(change_range.end);
|
||||
|
||||
text.truncate(change_range.start);
|
||||
text.extend(preedit_string.chars());
|
||||
text.append(&mut old_text_tail);
|
||||
}
|
||||
|
||||
// Send the event.
|
||||
let cursor_byte_pos = calc_byte_position(&ic_data.text, ic_data.cursor);
|
||||
let event = ImeEvent::Update(ic_data.text.iter().collect(), Some(cursor_byte_pos));
|
||||
|
||||
self.ime_events.push_back((ic_data.window.id, event));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_caret(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
position: &mut i32,
|
||||
direction: xim::CaretDirection,
|
||||
_style: xim::CaretStyle,
|
||||
) -> Result<(), ClientError> {
|
||||
// We only care about absolute position.
|
||||
if matches!(direction, xim::CaretDirection::AbsolutePosition) {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
ic_data.cursor = *position as usize;
|
||||
|
||||
// Send the event
|
||||
let event =
|
||||
ImeEvent::Update(ic_data.text.iter().collect(), Some(*position as usize));
|
||||
self.ime_events.push_back((ic_data.window.id, event));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_done(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the client data.
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// We're done with a preedit.
|
||||
ic_data.text.clear();
|
||||
ic_data.cursor = 0;
|
||||
|
||||
// Send a message to the window.
|
||||
let window = ic_data.window.id;
|
||||
self.ime_events.push_back((window, ImeEvent::End));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_commit(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
text: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the client data.
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Send a message to the window.
|
||||
let window = ic_data.window.id;
|
||||
self.ime_events
|
||||
.push_back((window, ImeEvent::Commit(text.to_owned())));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_query_extension(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_extensions: &[xim::Extension],
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_forward_event(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
_flag: xim::ForwardEventFlag,
|
||||
_xev: C::XEvent,
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_event_mask(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
_forward_event_mask: u32,
|
||||
_synchronous_event_mask: u32,
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn invalid_state(state: InvalidImeState) -> ClientError {
|
||||
ClientError::Other(Box::new(X11Error::InvalidImeState(state)))
|
||||
}
|
||||
|
||||
struct ImData(Option<&'static str>);
|
||||
|
||||
impl fmt::Debug for ImData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
Some(name) => write!(f, "\"{}\"", name),
|
||||
None => write!(f, "default input method"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current locale.
|
||||
fn locale() -> String {
|
||||
use std::ffi::CStr;
|
||||
|
||||
const EN_US: &str = "en_US.UTF-8";
|
||||
|
||||
// Get the pointer to the current locale.
|
||||
let locale_ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) };
|
||||
|
||||
// If locale_ptr is null, just default to en_US.UTF-8.
|
||||
if locale_ptr.is_null() {
|
||||
return EN_US.to_owned();
|
||||
}
|
||||
|
||||
// Convert the pointer to a CStr.
|
||||
let locale_cstr = unsafe { CStr::from_ptr(locale_ptr) };
|
||||
|
||||
// Convert the CStr to a String to prevent the result from getting clobbered.
|
||||
locale_cstr.to_str().unwrap_or(EN_US).to_owned()
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter().take(pos).map(|c| c.len_utf8()).sum()
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{
|
||||
context::{ImeContext, ImeContextCreationError},
|
||||
inner::{close_im, ImeInner},
|
||||
input_method::PotentialInputMethods,
|
||||
};
|
||||
|
||||
pub(crate) unsafe fn xim_set_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
xim: ffi::XIM,
|
||||
field: *const c_char,
|
||||
callback: *mut ffi::XIMCallback,
|
||||
) -> Result<(), XError> {
|
||||
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
|
||||
// access that isn't type-checked.
|
||||
unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
// Set a callback for when an input method matching the current locale modifiers becomes
|
||||
// available. Note that this has nothing to do with what input methods are open or able to be
|
||||
// opened, and simply uses the modifiers that are set when the callback is set.
|
||||
// * This is called per locale modifier, not per input method opened with that locale modifier.
|
||||
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
|
||||
// input contexts would always silently fail to use the input method.
|
||||
pub(crate) unsafe fn set_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XRegisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn unset_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnregisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_destroy_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
inner: &ImeInner,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
xim_set_callback(
|
||||
xconn,
|
||||
im,
|
||||
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
||||
&inner.destroy_callback as *const _ as *mut _,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ReplaceImError {
|
||||
// Boxed to prevent large error type
|
||||
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
|
||||
// includes replacing all existing input contexts and free'ing resources as necessary. This only
|
||||
// modifies existing state if all operations succeed.
|
||||
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
|
||||
let (new_im, is_fallback) = {
|
||||
let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
|
||||
let is_fallback = new_im.is_fallback();
|
||||
(
|
||||
new_im.ok().ok_or_else(|| {
|
||||
ReplaceImError::MethodOpenFailed(Box::new(unsafe {
|
||||
(*inner).potential_input_methods.clone()
|
||||
}))
|
||||
})?,
|
||||
is_fallback,
|
||||
)
|
||||
};
|
||||
|
||||
// It's important to always set a destroy callback, since there's otherwise potential for us
|
||||
// to try to use or free a resource that's already been destroyed on the server.
|
||||
{
|
||||
let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
|
||||
if result.is_err() {
|
||||
let _ = unsafe { close_im(xconn, new_im.im) };
|
||||
}
|
||||
result
|
||||
}
|
||||
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
|
||||
|
||||
let mut new_contexts = HashMap::new();
|
||||
for (window, old_context) in unsafe { (*inner).contexts.iter() } {
|
||||
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
|
||||
|
||||
// Check if the IME was allowed on that context.
|
||||
let is_allowed = old_context
|
||||
.as_ref()
|
||||
.map(|old_context| old_context.is_allowed())
|
||||
.unwrap_or_default();
|
||||
|
||||
// We can't use the style from the old context here, since it may change on reload, so
|
||||
// pick style from the new XIM based on the old state.
|
||||
let style = if is_allowed {
|
||||
new_im.preedit_style
|
||||
} else {
|
||||
new_im.none_style
|
||||
};
|
||||
|
||||
let new_context = {
|
||||
let result = unsafe {
|
||||
ImeContext::new(
|
||||
xconn,
|
||||
new_im.im,
|
||||
style,
|
||||
*window,
|
||||
spot,
|
||||
(*inner).event_sender.clone(),
|
||||
)
|
||||
};
|
||||
if result.is_err() {
|
||||
let _ = unsafe { close_im(xconn, new_im.im) };
|
||||
}
|
||||
result.map_err(ReplaceImError::ContextCreationFailed)?
|
||||
};
|
||||
new_contexts.insert(*window, Some(new_context));
|
||||
}
|
||||
|
||||
// If we've made it this far, everything succeeded.
|
||||
unsafe {
|
||||
let _ = (*inner).destroy_all_contexts_if_necessary();
|
||||
let _ = (*inner).close_im_if_necessary();
|
||||
(*inner).im = Some(new_im);
|
||||
(*inner).contexts = new_contexts;
|
||||
(*inner).is_destroyed = false;
|
||||
(*inner).is_fallback = is_fallback;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xim_instantiate_callback(
|
||||
_display: *mut ffi::Display,
|
||||
client_data: ffi::XPointer,
|
||||
// This field is unsupplied.
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
},
|
||||
Err(err) => unsafe {
|
||||
if (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to reopen input method: {err:?}");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This callback is triggered when the input method is closed on the server end. When this
|
||||
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
|
||||
// free'd (attempting to do so causes our connection to freeze).
|
||||
pub unsafe extern "C" fn xim_destroy_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
// This field is unsupplied.
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
unsafe { (*inner).is_destroyed = true };
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
if unsafe { !(*inner).is_fallback } {
|
||||
let _ = unsafe { set_instantiate_callback(xconn, client_data) };
|
||||
// Attempt to open fallback input method.
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe { (*inner).is_fallback = true },
|
||||
Err(err) => {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to open fallback input method: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_short;
|
||||
use std::sync::Arc;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
/// IME creation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ImeContextCreationError {
|
||||
/// Got the error from Xlib.
|
||||
XError(XError),
|
||||
|
||||
/// Got null pointer from Xlib but without exact reason.
|
||||
Null,
|
||||
}
|
||||
|
||||
/// The callback used by XIM preedit functions.
|
||||
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
|
||||
|
||||
/// Wrapper for creating XIM callbacks.
|
||||
#[inline]
|
||||
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
|
||||
XIMCallback {
|
||||
client_data,
|
||||
callback: Some(callback),
|
||||
}
|
||||
}
|
||||
|
||||
/// The server started preedit.
|
||||
extern "C" fn preedit_start_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) -> i32 {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
client_data.text.clear();
|
||||
client_data.cursor_pos = 0;
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::Start))
|
||||
.expect("failed to send preedit start event");
|
||||
-1
|
||||
}
|
||||
|
||||
/// Done callback is used when the preedit should be hidden.
|
||||
extern "C" fn preedit_done_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
// Drop text buffer and reset cursor position on done.
|
||||
client_data.text = Vec::new();
|
||||
client_data.cursor_pos = 0;
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::End))
|
||||
.expect("failed to send preedit end event");
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter()
|
||||
.take(pos)
|
||||
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
|
||||
}
|
||||
|
||||
/// Preedit text information to be drawn inline by the client.
|
||||
extern "C" fn preedit_draw_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
|
||||
client_data.cursor_pos = call_data.caret as usize;
|
||||
|
||||
let chg_range =
|
||||
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
|
||||
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
|
||||
warn!(
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
|
||||
client_data.text.len(),
|
||||
call_data.chg_first,
|
||||
call_data.chg_length
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// NULL indicate text deletion
|
||||
let mut new_chars = if call_data.text.is_null() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let xim_text = unsafe { &mut *(call_data.text) };
|
||||
if xim_text.encoding_is_wchar > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { xim_text.string.multi_byte };
|
||||
|
||||
if new_text.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { CStr::from_ptr(new_text) };
|
||||
|
||||
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
|
||||
.chars()
|
||||
.collect()
|
||||
};
|
||||
let mut old_text_tail = client_data.text.split_off(chg_range.end);
|
||||
client_data.text.truncate(chg_range.start);
|
||||
client_data.text.append(&mut new_chars);
|
||||
client_data.text.append(&mut old_text_tail);
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
|
||||
/// Handling of cursor movements in preedit text.
|
||||
extern "C" fn preedit_caret_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
|
||||
|
||||
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
|
||||
client_data.cursor_pos = call_data.position as usize;
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to simplify callback creation and latter passing into Xlib XIM.
|
||||
struct PreeditCallbacks {
|
||||
start_callback: ffi::XIMCallback,
|
||||
done_callback: ffi::XIMCallback,
|
||||
draw_callback: ffi::XIMCallback,
|
||||
caret_callback: ffi::XIMCallback,
|
||||
}
|
||||
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute(preedit_start_callback as usize)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
|
||||
|
||||
PreeditCallbacks {
|
||||
start_callback,
|
||||
done_callback,
|
||||
caret_callback,
|
||||
draw_callback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeContextClientData {
|
||||
window: ffi::Window,
|
||||
event_sender: ImeEventSender,
|
||||
text: Vec<char>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
// XXX: this struct doesn't destroy its XIC resource when dropped.
|
||||
// This is intentional, as it doesn't have enough information to know whether or not the context
|
||||
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
|
||||
// through `ImeInner`.
|
||||
pub struct ImeContext {
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_spot: ffi::XPoint,
|
||||
pub(crate) style: Style,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
|
||||
// there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
}
|
||||
|
||||
impl ImeContext {
|
||||
pub(crate) unsafe fn new(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: Style,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
event_sender,
|
||||
text: Vec::new(),
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let ic = match style as _ {
|
||||
Style::Preedit(style) => unsafe {
|
||||
ImeContext::create_preedit_ic(
|
||||
xconn,
|
||||
im,
|
||||
style,
|
||||
window,
|
||||
client_data as ffi::XPointer,
|
||||
)
|
||||
},
|
||||
Style::Nothing(style) => unsafe {
|
||||
ImeContext::create_nothing_ic(xconn, im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
|
||||
}
|
||||
.ok_or(ImeContextCreationError::Null)?;
|
||||
|
||||
xconn
|
||||
.check_errors()
|
||||
.map_err(ImeContextCreationError::XError)?;
|
||||
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
style,
|
||||
_client_data: unsafe { Box::from_raw(client_data) },
|
||||
};
|
||||
|
||||
// Set the spot location, if it's present.
|
||||
if let Some(ic_spot) = ic_spot {
|
||||
context.set_spot(xconn, ic_spot.x, ic_spot.y)
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
unsafe fn create_none_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_preedit_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Option<ffi::XIC> {
|
||||
let preedit_callbacks = PreeditCallbacks::new(client_data);
|
||||
let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.start_callback) as *const _,
|
||||
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.done_callback) as *const _,
|
||||
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.caret_callback) as *const _,
|
||||
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.draw_callback) as *const _,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
})
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_nothing_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XSetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnsetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> bool {
|
||||
!matches!(self.style, Style::None(_))
|
||||
}
|
||||
|
||||
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
||||
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
|
||||
// window and couldn't be changed.
|
||||
//
|
||||
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
|
||||
pub(crate) fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
||||
if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ic_spot = ffi::XPoint { x, y };
|
||||
|
||||
unsafe {
|
||||
let preedit_attr = util::memory::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr(),
|
||||
&self.ic_spot,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
(xconn.xlib.XSetICValues)(
|
||||
self.ic,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
use std::{collections::HashMap, mem, sync::Arc};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{
|
||||
context::ImeContext,
|
||||
input_method::{InputMethod, PotentialInputMethods},
|
||||
};
|
||||
use crate::platform_impl::platform::x11::ime::ImeEventSender;
|
||||
|
||||
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XCloseIM)(im) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XDestroyIC)(ic) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) struct ImeInner {
|
||||
pub xconn: Arc<XConnection>,
|
||||
pub im: Option<InputMethod>,
|
||||
pub potential_input_methods: PotentialInputMethods,
|
||||
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
|
||||
// WARNING: this is initially zeroed!
|
||||
pub destroy_callback: ffi::XIMCallback,
|
||||
pub event_sender: ImeEventSender,
|
||||
// Indicates whether or not the the input method was destroyed on the server end
|
||||
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
|
||||
pub is_destroyed: bool,
|
||||
pub is_fallback: bool,
|
||||
}
|
||||
|
||||
impl ImeInner {
|
||||
pub(crate) fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
potential_input_methods: PotentialInputMethods,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Self {
|
||||
ImeInner {
|
||||
xconn,
|
||||
im: None,
|
||||
potential_input_methods,
|
||||
contexts: HashMap::new(),
|
||||
destroy_callback: unsafe { mem::zeroed() },
|
||||
event_sender,
|
||||
is_destroyed: false,
|
||||
is_fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||
if !self.is_destroyed && self.im.is_some() {
|
||||
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
|
||||
for context in self.contexts.values().flatten() {
|
||||
unsafe { self.destroy_ic_if_necessary(context.ic)? };
|
||||
}
|
||||
Ok(!self.is_destroyed)
|
||||
}
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
use std::{
|
||||
env,
|
||||
ffi::{CStr, CString, IntoStringError},
|
||||
fmt,
|
||||
os::raw::{c_char, c_ulong, c_ushort},
|
||||
ptr,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{super::atoms::*, ffi, util, XConnection, XError};
|
||||
use once_cell::sync::Lazy;
|
||||
use x11rb::protocol::xproto;
|
||||
|
||||
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
|
||||
|
||||
unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
|
||||
let _lock = GLOBAL_LOCK.lock();
|
||||
|
||||
// XSetLocaleModifiers returns...
|
||||
// * The current locale modifiers if it's given a NULL pointer.
|
||||
// * The new locale modifiers if we succeeded in setting them.
|
||||
// * NULL if the locale modifiers string is malformed or if the
|
||||
// current locale is not supported by Xlib.
|
||||
unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
|
||||
|
||||
let im = unsafe {
|
||||
(xconn.xlib.XOpenIM)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if im.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(im)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputMethod {
|
||||
pub im: ffi::XIM,
|
||||
pub preedit_style: Style,
|
||||
pub none_style: Style,
|
||||
_name: String,
|
||||
}
|
||||
|
||||
impl InputMethod {
|
||||
fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
|
||||
let mut styles: *mut XIMStyles = std::ptr::null_mut();
|
||||
|
||||
// Query the styles supported by the XIM.
|
||||
unsafe {
|
||||
if !(xconn.xlib.XGetIMValues)(
|
||||
im,
|
||||
ffi::XNQueryInputStyle_0.as_ptr() as *const _,
|
||||
(&mut styles) as *mut _,
|
||||
std::ptr::null_mut::<()>(),
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut preedit_style = None;
|
||||
let mut none_style = None;
|
||||
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
|
||||
.iter()
|
||||
.for_each(|style| match *style {
|
||||
XIM_PREEDIT_STYLE => {
|
||||
preedit_style = Some(Style::Preedit(*style));
|
||||
}
|
||||
XIM_NOTHING_STYLE if preedit_style.is_none() => {
|
||||
preedit_style = Some(Style::Nothing(*style))
|
||||
}
|
||||
XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
|
||||
_ => (),
|
||||
});
|
||||
|
||||
(xconn.xlib.XFree)(styles.cast());
|
||||
};
|
||||
|
||||
if preedit_style.is_none() && none_style.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
|
||||
let none_style = none_style.unwrap_or(preedit_style);
|
||||
|
||||
Some(InputMethod {
|
||||
im,
|
||||
_name: name,
|
||||
preedit_style,
|
||||
none_style,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
|
||||
|
||||
/// Style of the IME context.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
/// Preedit callbacks.
|
||||
Preedit(XIMStyle),
|
||||
|
||||
/// Nothing.
|
||||
Nothing(XIMStyle),
|
||||
|
||||
/// No IME.
|
||||
None(XIMStyle),
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style::None(XIM_NONE_STYLE)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct XIMStyles {
|
||||
count_styles: c_ushort,
|
||||
supported_styles: *const XIMStyle,
|
||||
}
|
||||
|
||||
pub(crate) type XIMStyle = c_ulong;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputMethodResult {
|
||||
/// Input method used locale modifier from `XMODIFIERS` environment variable.
|
||||
XModifiers(InputMethod),
|
||||
/// Input method used internal fallback locale modifier.
|
||||
Fallback(InputMethod),
|
||||
/// Input method could not be opened using any locale modifier tried.
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl InputMethodResult {
|
||||
pub fn is_fallback(&self) -> bool {
|
||||
matches!(self, InputMethodResult::Fallback(_))
|
||||
}
|
||||
|
||||
pub fn ok(self) -> Option<InputMethod> {
|
||||
use self::InputMethodResult::*;
|
||||
match self {
|
||||
XModifiers(im) | Fallback(im) => Some(im),
|
||||
Failure => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum GetXimServersError {
|
||||
XError(#[allow(dead_code)] XError),
|
||||
GetPropertyError(#[allow(dead_code)] util::GetPropertyError),
|
||||
InvalidUtf8(#[allow(dead_code)] IntoStringError),
|
||||
}
|
||||
|
||||
impl From<util::GetPropertyError> for GetXimServersError {
|
||||
fn from(error: util::GetPropertyError) -> Self {
|
||||
GetXimServersError::GetPropertyError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting
|
||||
// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named
|
||||
// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
|
||||
// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
|
||||
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
|
||||
// XMODIFIERS to `@server=ibus`?!?"
|
||||
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
|
||||
let atoms = xconn.atoms();
|
||||
let servers_atom = atoms[XIM_SERVERS];
|
||||
|
||||
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
||||
|
||||
let mut atoms: Vec<ffi::Atom> = xconn
|
||||
.get_property::<xproto::Atom>(
|
||||
root as xproto::Window,
|
||||
servers_atom,
|
||||
xproto::Atom::from(xproto::AtomEnum::ATOM),
|
||||
)
|
||||
.map_err(GetXimServersError::GetPropertyError)?
|
||||
.into_iter()
|
||||
.map(ffi::Atom::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
|
||||
unsafe {
|
||||
(xconn.xlib.XGetAtomNames)(
|
||||
xconn.display,
|
||||
atoms.as_mut_ptr(),
|
||||
atoms.len() as _,
|
||||
names.as_mut_ptr() as _,
|
||||
)
|
||||
};
|
||||
unsafe { names.set_len(atoms.len()) };
|
||||
|
||||
let mut formatted_names = Vec::with_capacity(names.len());
|
||||
for name in names {
|
||||
let string = unsafe { CStr::from_ptr(name) }
|
||||
.to_owned()
|
||||
.into_string()
|
||||
.map_err(GetXimServersError::InvalidUtf8)?;
|
||||
unsafe { (xconn.xlib.XFree)(name as _) };
|
||||
formatted_names.push(string.replace("@server=", "@im="));
|
||||
}
|
||||
xconn.check_errors().map_err(GetXimServersError::XError)?;
|
||||
Ok(formatted_names)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InputMethodName {
|
||||
c_string: CString,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl InputMethodName {
|
||||
pub fn from_string(string: String) -> Self {
|
||||
let c_string = CString::new(string.clone())
|
||||
.expect("String used to construct CString contained null byte");
|
||||
InputMethodName { c_string, string }
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
let c_string =
|
||||
CString::new(string).expect("String used to construct CString contained null byte");
|
||||
InputMethodName {
|
||||
c_string,
|
||||
string: string.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for InputMethodName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.string.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PotentialInputMethod {
|
||||
name: InputMethodName,
|
||||
successful: Option<bool>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethod {
|
||||
pub fn from_string(string: String) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_string(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_str(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.successful = None;
|
||||
}
|
||||
|
||||
pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
|
||||
let im = unsafe { open_im(xconn, &self.name.c_string) };
|
||||
self.successful = Some(im.is_some());
|
||||
im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
|
||||
// came from, and if it succeeded.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PotentialInputMethods {
|
||||
// On correctly configured systems, the XMODIFIERS environment variable tells us everything we
|
||||
// need to know.
|
||||
xmodifiers: Option<PotentialInputMethod>,
|
||||
// We have some standard options at our disposal that should ostensibly always work. For users
|
||||
// who only need compose sequences, this ensures that the program launches without a hitch
|
||||
// For users who need more sophisticated IME features, this is more or less a silent failure.
|
||||
// Logging features should be added in the future to allow both audiences to be effectively
|
||||
// served.
|
||||
fallbacks: [PotentialInputMethod; 2],
|
||||
// For diagnostic purposes, we include the list of XIM servers that the server reports as
|
||||
// being available.
|
||||
_xim_servers: Result<Vec<String>, GetXimServersError>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethods {
|
||||
pub fn new(xconn: &Arc<XConnection>) -> Self {
|
||||
let xmodifiers = env::var("XMODIFIERS")
|
||||
.ok()
|
||||
.map(PotentialInputMethod::from_string);
|
||||
PotentialInputMethods {
|
||||
// Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
|
||||
// XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
|
||||
// running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
|
||||
// defined in the profile (or parent environment) then that parent XMODIFIERS is used.
|
||||
// If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
|
||||
// XSetLocaleModifiers uses the default local input method. Note that defining
|
||||
// XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
|
||||
// that case, we get `None` and end up skipping ahead to the next method.
|
||||
xmodifiers,
|
||||
fallbacks: [
|
||||
// This is a standard input method that supports compose sequences, which should
|
||||
// always be available. `@im=none` appears to mean the same thing.
|
||||
PotentialInputMethod::from_str("@im=local"),
|
||||
// This explicitly specifies to use the implementation-dependent default, though
|
||||
// that seems to be equivalent to just using the local input method.
|
||||
PotentialInputMethod::from_str("@im="),
|
||||
],
|
||||
// The XIM_SERVERS property can have surprising values. For instance, when I exited
|
||||
// ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
|
||||
// that the fcitx input method could only be successfully opened using "@im=ibus".
|
||||
// Presumably due to this quirk, it's actually possible to alternate between ibus and
|
||||
// fcitx in a running application.
|
||||
_xim_servers: unsafe { get_xim_servers(xconn) },
|
||||
}
|
||||
}
|
||||
|
||||
// This resets the `successful` field of every potential input method, ensuring we have
|
||||
// accurate information when this struct is re-used by the destruction/instantiation callbacks.
|
||||
fn reset(&mut self) {
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
input_method.reset();
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
input_method.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_im(
|
||||
&mut self,
|
||||
xconn: &Arc<XConnection>,
|
||||
callback: Option<&dyn Fn()>,
|
||||
) -> InputMethodResult {
|
||||
use self::InputMethodResult::*;
|
||||
|
||||
self.reset();
|
||||
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return XModifiers(im);
|
||||
} else if let Some(ref callback) = callback {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return Fallback(im);
|
||||
}
|
||||
}
|
||||
|
||||
Failure
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
// Important: all XIM calls need to happen from the same thread!
|
||||
|
||||
mod callbacks;
|
||||
mod context;
|
||||
mod inner;
|
||||
mod input_method;
|
||||
|
||||
use std::sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
pub use self::context::ImeContextCreationError;
|
||||
use self::{
|
||||
callbacks::*,
|
||||
context::ImeContext,
|
||||
inner::{close_im, ImeInner},
|
||||
input_method::{PotentialInputMethods, Style},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ImeEvent {
|
||||
Enabled,
|
||||
Start,
|
||||
Update(String, usize),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub type ImeReceiver = Receiver<ImeRequest>;
|
||||
pub type ImeSender = Sender<ImeRequest>;
|
||||
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
|
||||
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
|
||||
|
||||
/// Request to control XIM handler from the window.
|
||||
pub enum ImeRequest {
|
||||
/// Set IME spot position for given `window_id`.
|
||||
Position(ffi::Window, i16, i16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(ffi::Window, bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ImeCreationError {
|
||||
// Boxed to prevent large error type
|
||||
OpenFailure(Box<PotentialInputMethods>),
|
||||
SetDestroyCallbackFailed(#[allow(dead_code)] XError),
|
||||
}
|
||||
|
||||
pub(crate) struct Ime {
|
||||
xconn: Arc<XConnection>,
|
||||
// The actual meat of this struct is boxed away, since it needs to have a fixed location in
|
||||
// memory so we can pass a pointer to it around.
|
||||
inner: Box<ImeInner>,
|
||||
}
|
||||
|
||||
impl Ime {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeCreationError> {
|
||||
let potential_input_methods = PotentialInputMethods::new(&xconn);
|
||||
|
||||
let (mut inner, client_data) = {
|
||||
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
|
||||
let inner_ptr = Box::into_raw(inner);
|
||||
let client_data = inner_ptr as _;
|
||||
let destroy_callback = ffi::XIMCallback {
|
||||
client_data,
|
||||
callback: Some(xim_destroy_callback),
|
||||
};
|
||||
inner = unsafe { Box::from_raw(inner_ptr) };
|
||||
inner.destroy_callback = destroy_callback;
|
||||
(inner, client_data)
|
||||
};
|
||||
|
||||
let xconn = Arc::clone(&inner.xconn);
|
||||
|
||||
let input_method = inner.potential_input_methods.open_im(
|
||||
&xconn,
|
||||
Some(&|| {
|
||||
let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
|
||||
}),
|
||||
);
|
||||
|
||||
let is_fallback = input_method.is_fallback();
|
||||
if let Some(input_method) = input_method.ok() {
|
||||
inner.is_fallback = is_fallback;
|
||||
unsafe {
|
||||
let result = set_destroy_callback(&xconn, input_method.im, &inner)
|
||||
.map_err(ImeCreationError::SetDestroyCallbackFailed);
|
||||
if result.is_err() {
|
||||
let _ = close_im(&xconn, input_method.im);
|
||||
}
|
||||
result?;
|
||||
}
|
||||
inner.im = Some(input_method);
|
||||
Ok(Ime { xconn, inner })
|
||||
} else {
|
||||
Err(ImeCreationError::OpenFailure(Box::new(
|
||||
inner.potential_input_methods,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_destroyed(&self) -> bool {
|
||||
self.inner.is_destroyed
|
||||
}
|
||||
|
||||
// This pattern is used for various methods here:
|
||||
// Ok(_) indicates that nothing went wrong internally
|
||||
// Ok(true) indicates that the action was actually performed
|
||||
// Ok(false) indicates that the action is not presently applicable
|
||||
pub fn create_context(
|
||||
&mut self,
|
||||
window: ffi::Window,
|
||||
with_preedit: bool,
|
||||
) -> Result<bool, ImeContextCreationError> {
|
||||
let context = if self.is_destroyed() {
|
||||
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
|
||||
None
|
||||
} else {
|
||||
let im = self.inner.im.as_ref().unwrap();
|
||||
let style = if with_preedit {
|
||||
im.preedit_style
|
||||
} else {
|
||||
im.none_style
|
||||
};
|
||||
|
||||
let context = unsafe {
|
||||
ImeContext::new(
|
||||
&self.inner.xconn,
|
||||
im.im,
|
||||
style,
|
||||
window,
|
||||
None,
|
||||
self.inner.event_sender.clone(),
|
||||
)?
|
||||
};
|
||||
|
||||
// Check the state on the context, since it could fail to enable or disable preedit.
|
||||
let event = if matches!(style, Style::None(_)) {
|
||||
if with_preedit {
|
||||
debug!("failed to create IME context with preedit support.")
|
||||
}
|
||||
ImeEvent::Disabled
|
||||
} else {
|
||||
if !with_preedit {
|
||||
debug!("failed to create IME context without preedit support.")
|
||||
}
|
||||
ImeEvent::Enabled
|
||||
};
|
||||
|
||||
self.inner
|
||||
.event_sender
|
||||
.send((window, event))
|
||||
.expect("Failed to send enabled event");
|
||||
|
||||
Some(context)
|
||||
};
|
||||
|
||||
self.inner.contexts.insert(window, context);
|
||||
Ok(!self.is_destroyed())
|
||||
}
|
||||
|
||||
pub fn get_context(&self, window: ffi::Window) -> Option<ffi::XIC> {
|
||||
if self.is_destroyed() {
|
||||
return None;
|
||||
}
|
||||
if let Some(Some(context)) = self.inner.contexts.get(&window) {
|
||||
Some(context.ic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if let Some(Some(context)) = self.inner.contexts.remove(&window) {
|
||||
unsafe {
|
||||
self.inner.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.focus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unfocus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.unfocus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.set_spot(&self.xconn, x as _, y as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
if allowed == context.is_allowed() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove context for that window.
|
||||
let _ = self.remove_context(window);
|
||||
|
||||
// Create new context supporting IME input.
|
||||
let _ = self.create_context(window, allowed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ime {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.inner.destroy_all_contexts_if_necessary();
|
||||
let _ = self.inner.close_im_if_necessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,5 @@
|
||||
#![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;
|
||||
@@ -54,24 +10,71 @@ mod monitor;
|
||||
pub mod util;
|
||||
mod window;
|
||||
mod xdisplay;
|
||||
mod xsettings;
|
||||
|
||||
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,
|
||||
ops::Deref,
|
||||
os::{
|
||||
raw::*,
|
||||
unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
|
||||
},
|
||||
rc::Rc,
|
||||
slice, str,
|
||||
sync::mpsc::{Receiver, Sender, TryRecvError},
|
||||
sync::{mpsc, Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use atoms::*;
|
||||
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;
|
||||
|
||||
use x11rb::protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
xproto::{self, ConnectionExt as _},
|
||||
};
|
||||
use x11rb::x11_utils::X11Error as LogicalError;
|
||||
use x11rb::{
|
||||
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
|
||||
xcb_ffi::ReplyOrIdError,
|
||||
};
|
||||
|
||||
use self::{
|
||||
dnd::{Dnd, DndState},
|
||||
event_processor::EventProcessor,
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
// 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>>;
|
||||
|
||||
struct WakeSender<T> {
|
||||
@@ -136,15 +139,18 @@ pub struct EventLoopWindowTarget<T> {
|
||||
xconn: Arc<XConnection>,
|
||||
wm_delete_window: xproto::Atom,
|
||||
net_wm_ping: xproto::Atom,
|
||||
ime_sender: ImeSender,
|
||||
ime_sender: mpsc::Sender<ime::ImeRequest>,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
exit: Cell<Option<i32>>,
|
||||
root: xproto::Window,
|
||||
ime: Option<RefCell<Ime>>,
|
||||
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
activation_sender: WakeSender<ActivationToken>,
|
||||
device_events: Cell<DeviceEvents>,
|
||||
|
||||
/// State of IME.
|
||||
ime: Option<RefCell<ime::ImeData>>,
|
||||
|
||||
_marker: ::std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -157,6 +163,7 @@ 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,
|
||||
@@ -189,59 +196,26 @@ impl<T: 'static> EventLoop<T> {
|
||||
let wm_delete_window = atoms[WM_DELETE_WINDOW];
|
||||
let net_wm_ping = atoms[_NET_WM_PING];
|
||||
|
||||
// Create an event queue.
|
||||
let event_queue = Rc::new(RefCell::new(std::collections::VecDeque::with_capacity(4)));
|
||||
|
||||
let dnd = Dnd::new(Arc::clone(&xconn))
|
||||
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
||||
|
||||
let (ime_sender, ime_receiver) = mpsc::channel();
|
||||
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
|
||||
// Input methods will open successfully without setting the locale, but it won't be
|
||||
// possible to actually commit pre-edit sequences.
|
||||
unsafe {
|
||||
// Remember default locale to restore it if target locale is unsupported
|
||||
// by Xlib
|
||||
let default_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
|
||||
|
||||
// Check if set locale is supported by Xlib.
|
||||
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
|
||||
// will fail.
|
||||
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
|
||||
if !locale_supported {
|
||||
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
warn!(
|
||||
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
|
||||
CStr::from_ptr(unsupported_locale).to_string_lossy(),
|
||||
CStr::from_ptr(default_locale).to_string_lossy()
|
||||
);
|
||||
// Restore default locale
|
||||
setlocale(LC_CTYPE, default_locale);
|
||||
let ime = match ime::ImeData::new(&xconn, xconn.default_screen_index(), &event_queue) {
|
||||
Ok(ime) => Some(ime),
|
||||
Err(e) => {
|
||||
log::error!("Failed to open IME: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
xconn
|
||||
.select_xrandr_input(root)
|
||||
.expect("Failed to query XRandR extension");
|
||||
|
||||
let xi2ext = xconn
|
||||
.xcb_connection()
|
||||
.extension_information(xinput::X11_EXTENSION_NAME)
|
||||
.expect("Failed to query XInput extension")
|
||||
.expect("X server missing XInput extension");
|
||||
let xkbext = xconn
|
||||
.xcb_connection()
|
||||
.extension_information(xkb::X11_EXTENSION_NAME)
|
||||
.expect("Failed to query XKB extension")
|
||||
.expect("X server missing XKB extension");
|
||||
|
||||
// Check for XInput2 support.
|
||||
xconn
|
||||
.xcb_connection()
|
||||
@@ -289,19 +263,16 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Create a channel for sending user events.
|
||||
let (user_sender, user_channel) = mpsc::channel();
|
||||
|
||||
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 kb_state =
|
||||
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
ime,
|
||||
root,
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
exit: Cell::new(None),
|
||||
windows: Default::default(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
ime: ime.map(RefCell::new),
|
||||
ime_sender,
|
||||
xconn,
|
||||
wm_delete_window,
|
||||
@@ -320,36 +291,29 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Set initial device event filter.
|
||||
window_target.update_listen_device_events(true);
|
||||
|
||||
let root_window_target = RootELW {
|
||||
let target = Rc::new(RootELW {
|
||||
p: super::EventLoopWindowTarget::X(window_target),
|
||||
_marker: PhantomData,
|
||||
};
|
||||
_marker: ::std::marker::PhantomData,
|
||||
});
|
||||
|
||||
let event_processor = EventProcessor {
|
||||
target: root_window_target,
|
||||
event_queue,
|
||||
target: target.clone(),
|
||||
dnd,
|
||||
devices: Default::default(),
|
||||
randr_event_offset,
|
||||
ime_receiver,
|
||||
ime_event_receiver,
|
||||
xi2ext,
|
||||
xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN),
|
||||
xmodmap,
|
||||
xkbext,
|
||||
xkb_context,
|
||||
ime_requests: ime_receiver,
|
||||
kb_state,
|
||||
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`)
|
||||
let xconn = &EventProcessor::window_target(&event_processor.target).xconn;
|
||||
|
||||
xconn
|
||||
get_xtarget(&target)
|
||||
.xconn
|
||||
.select_xinput_events(
|
||||
root,
|
||||
ALL_DEVICES,
|
||||
@@ -357,12 +321,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
)
|
||||
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
|
||||
|
||||
xconn
|
||||
get_xtarget(&target)
|
||||
.xconn
|
||||
.select_xkb_events(
|
||||
0x100, // Use the "core keyboard device"
|
||||
xkb::EventType::NEW_KEYBOARD_NOTIFY
|
||||
| xkb::EventType::MAP_NOTIFY
|
||||
| xkb::EventType::STATE_NOTIFY,
|
||||
xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -377,6 +340,7 @@ 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,
|
||||
},
|
||||
@@ -393,7 +357,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
pub(crate) fn window_target(&self) -> &RootELW<T> {
|
||||
&self.event_processor.target
|
||||
&self.target
|
||||
}
|
||||
|
||||
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
|
||||
@@ -422,7 +386,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 = EventProcessor::window_target(&self.event_processor.target);
|
||||
let wt = get_xtarget(&self.target);
|
||||
wt.x_connection().sync_with_server().map_err(|x_err| {
|
||||
EventLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err)))))
|
||||
})?;
|
||||
@@ -545,12 +509,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<T>, &RootELW<T>),
|
||||
{
|
||||
callback(Event::NewEvents(cause), &self.event_processor.target);
|
||||
callback(crate::event::Event::NewEvents(cause), &self.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(Event::Resumed, &self.event_processor.target);
|
||||
callback(crate::event::Event::Resumed, &self.target);
|
||||
}
|
||||
|
||||
// Process all pending events
|
||||
@@ -565,16 +529,16 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
|
||||
match token {
|
||||
Some(Ok(token)) => {
|
||||
let event = Event::WindowEvent {
|
||||
Some(Ok(token)) => callback(
|
||||
crate::event::Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::ActivationTokenDone {
|
||||
event: crate::event::WindowEvent::ActivationTokenDone {
|
||||
serial,
|
||||
token: crate::window::ActivationToken::_new(token),
|
||||
},
|
||||
};
|
||||
callback(event, &self.event_processor.target)
|
||||
}
|
||||
},
|
||||
&self.target,
|
||||
),
|
||||
Some(Err(e)) => {
|
||||
log::error!("Failed to get activation token: {}", e);
|
||||
}
|
||||
@@ -585,7 +549,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Empty the user event buffer
|
||||
{
|
||||
while let Ok(event) = self.user_receiver.try_recv() {
|
||||
callback(Event::UserEvent(event), &self.event_processor.target);
|
||||
callback(crate::event::Event::UserEvent(event), &self.target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,14 +568,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_id,
|
||||
event: WindowEvent::RedrawRequested,
|
||||
},
|
||||
&self.event_processor.target,
|
||||
&self.target,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This is always the last event we dispatch before poll again
|
||||
{
|
||||
callback(Event::AboutToWait, &self.event_processor.target);
|
||||
callback(crate::event::Event::AboutToWait, &self.target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,44 +583,38 @@ impl<T: 'static> EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<T>, &RootELW<T>),
|
||||
{
|
||||
let mut xev = MaybeUninit::uninit();
|
||||
let target = &self.target;
|
||||
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, |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);
|
||||
}
|
||||
});
|
||||
while let Some(event) = self.event_processor.poll_one_event() {
|
||||
self.event_processor.process_event(event, |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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.control_flow()
|
||||
self.target.p.control_flow()
|
||||
}
|
||||
|
||||
fn exiting(&self) -> bool {
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.exiting()
|
||||
self.target.p.exiting()
|
||||
}
|
||||
|
||||
fn set_exit_code(&self, code: i32) {
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.set_exit_code(code);
|
||||
self.target.p.set_exit_code(code)
|
||||
}
|
||||
|
||||
fn exit_code(&self) -> Option<i32> {
|
||||
let window_target = EventProcessor::window_target(&self.event_processor.target);
|
||||
window_target.exit_code()
|
||||
self.target.p.exit_code()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,6 +630,14 @@ 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]
|
||||
@@ -745,10 +711,6 @@ 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()
|
||||
}
|
||||
@@ -877,8 +839,11 @@ pub enum X11Error {
|
||||
/// The XID range has been exhausted.
|
||||
XidsExhausted(IdsExhausted),
|
||||
|
||||
/// Got `null` from an Xlib function without a reason.
|
||||
UnexpectedNull(&'static str),
|
||||
/// An IME client error occurred.
|
||||
Ime(xim::ClientError),
|
||||
|
||||
/// The IME client has entered an invalid state.
|
||||
InvalidImeState(ime::InvalidImeState),
|
||||
|
||||
/// Got an invalid activation token.
|
||||
InvalidActivationToken(Vec<u8>),
|
||||
@@ -888,12 +853,6 @@ pub enum X11Error {
|
||||
|
||||
/// Could not find a matching X11 visual for this visualid
|
||||
NoSuchVisual(xproto::Visualid),
|
||||
|
||||
/// Unable to parse xsettings.
|
||||
XsettingsParse(xsettings::ParserError),
|
||||
|
||||
/// Failed to get property.
|
||||
GetProperty(util::GetPropertyError),
|
||||
}
|
||||
|
||||
impl fmt::Display for X11Error {
|
||||
@@ -903,9 +862,9 @@ impl fmt::Display for X11Error {
|
||||
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
|
||||
X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e),
|
||||
X11Error::Ime(e) => write!(f, "An IME error occurred: {}", e),
|
||||
X11Error::InvalidImeState(e) => write!(f, "Invalid IME state: {}", e),
|
||||
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
|
||||
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
|
||||
X11Error::InvalidActivationToken(s) => write!(
|
||||
f,
|
||||
"Invalid activation token: {}",
|
||||
@@ -919,9 +878,6 @@ impl fmt::Display for X11Error {
|
||||
visualid
|
||||
)
|
||||
}
|
||||
X11Error::XsettingsParse(err) => {
|
||||
write!(f, "Failed to parse xsettings: {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -971,15 +927,6 @@ impl From<ReplyError> for X11Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ime::ImeContextCreationError> for X11Error {
|
||||
fn from(value: ime::ImeContextCreationError) -> Self {
|
||||
match value {
|
||||
ime::ImeContextCreationError::XError(e) => e.into(),
|
||||
ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReplyOrIdError> for X11Error {
|
||||
fn from(value: ReplyOrIdError) -> Self {
|
||||
match value {
|
||||
@@ -990,17 +937,20 @@ impl From<ReplyOrIdError> for X11Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xsettings::ParserError> for X11Error {
|
||||
fn from(value: xsettings::ParserError) -> Self {
|
||||
Self::XsettingsParse(value)
|
||||
impl From<xim::ClientError> for X11Error {
|
||||
fn from(e: xim::ClientError) -> Self {
|
||||
match e {
|
||||
xim::ClientError::Other(other) => match other.downcast::<X11Error>() {
|
||||
Ok(x11) => *x11,
|
||||
Err(other) => X11Error::Ime(xim::ClientError::Other(other)),
|
||||
},
|
||||
e => X11Error::Ime(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<util::GetPropertyError> for X11Error {
|
||||
fn from(value: util::GetPropertyError) -> Self {
|
||||
Self::GetProperty(value)
|
||||
}
|
||||
}
|
||||
/// The underlying x11rb connection that we are using.
|
||||
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
|
||||
|
||||
/// Type alias for a void cookie.
|
||||
type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>;
|
||||
@@ -1025,7 +975,7 @@ fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
struct Device {
|
||||
_name: String,
|
||||
scroll_axes: Vec<(i32, ScrollAxis)>,
|
||||
// For master devices, this is the paired device (pointer <-> keyboard).
|
||||
@@ -1118,8 +1068,15 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the raw X11 representation for a 32-bit floating point to a double.
|
||||
/// Convert the raw X11 representation for a 32-bit fixed point to a double.
|
||||
#[inline]
|
||||
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
|
||||
(fp as f64) / ((1 << 16) as f64)
|
||||
}
|
||||
|
||||
/// Conver the raw X11 representation for a 64-bit fixed point number to a double.
|
||||
#[inline]
|
||||
fn xinput_fp3232_to_float(fp: xinput::Fp3232) -> f64 {
|
||||
let xinput::Fp3232 { integral, frac } = fp;
|
||||
integral as f64 + (frac as f64 / (1u64 << 32) as f64)
|
||||
}
|
||||
|
||||
@@ -234,8 +234,7 @@ 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, self.randr_version())?;
|
||||
let resources = ScreenResources::from_connection(self.xcb_connection(), root)?;
|
||||
|
||||
// Pipeline all of the get-crtc requests.
|
||||
let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
|
||||
@@ -285,16 +284,22 @@ impl XConnection {
|
||||
|
||||
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
|
||||
let mut monitors_lock = self.monitor_handles.lock().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)
|
||||
}
|
||||
}
|
||||
(*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()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -324,7 +329,7 @@ impl XConnection {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScreenResources {
|
||||
pub(crate) struct ScreenResources {
|
||||
/// List of attached modes.
|
||||
modes: Vec<randr::ModeInfo>,
|
||||
|
||||
@@ -344,9 +349,10 @@ 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> {
|
||||
if (major_version == 1 && minor_version >= 3) || major_version > 1 {
|
||||
let version = conn.randr_query_version(0, 0)?.reply()?;
|
||||
|
||||
if (version.major_version == 1 && version.minor_version >= 3) || version.major_version > 1 {
|
||||
let reply = conn
|
||||
.randr_get_screen_resources_current(root.root)?
|
||||
.reply()?;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,55 +0,0 @@
|
||||
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,9 +1,8 @@
|
||||
use std::ffi::CString;
|
||||
use std::iter;
|
||||
use std::{ffi::CString, iter, slice, sync::Arc};
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
|
||||
use crate::window::CursorIcon;
|
||||
use crate::{cursor::CursorImage, window::CursorIcon};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -20,6 +19,11 @@ impl XConnection {
|
||||
.expect("Failed to set cursor");
|
||||
}
|
||||
|
||||
pub fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) {
|
||||
self.update_cursor(window, cursor.inner.cursor)
|
||||
.expect("Failed to set cursor");
|
||||
}
|
||||
|
||||
fn create_empty_cursor(&self) -> ffi::Cursor {
|
||||
let data = 0;
|
||||
let pixmap = unsafe {
|
||||
@@ -87,3 +91,74 @@ impl XConnection {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SelectedCursor {
|
||||
Custom(CustomCursor),
|
||||
Named(CursorIcon),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CustomCursor {
|
||||
inner: Arc<CustomCursorInner>,
|
||||
}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) unsafe fn new(xconn: &Arc<XConnection>, image: &CursorImage) -> Self {
|
||||
unsafe {
|
||||
let ximage =
|
||||
(xconn.xcursor.XcursorImageCreate)(image.width as i32, image.height as i32);
|
||||
if ximage.is_null() {
|
||||
panic!("failed to allocate cursor image");
|
||||
}
|
||||
(*ximage).xhot = image.hotspot_x as u32;
|
||||
(*ximage).yhot = image.hotspot_y as u32;
|
||||
(*ximage).delay = 0;
|
||||
|
||||
let dst = slice::from_raw_parts_mut((*ximage).pixels, image.rgba.len() / 4);
|
||||
for (dst, chunk) in dst.iter_mut().zip(image.rgba.chunks_exact(4)) {
|
||||
*dst = (chunk[0] as u32) << 16
|
||||
| (chunk[1] as u32) << 8
|
||||
| (chunk[2] as u32)
|
||||
| (chunk[3] as u32) << 24;
|
||||
}
|
||||
|
||||
let cursor = (xconn.xcursor.XcursorImageLoadCursor)(xconn.display, ximage);
|
||||
(xconn.xcursor.XcursorImageDestroy)(ximage);
|
||||
Self {
|
||||
inner: Arc::new(CustomCursorInner {
|
||||
xconn: xconn.clone(),
|
||||
cursor,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CustomCursorInner {
|
||||
xconn: Arc<XConnection>,
|
||||
cursor: ffi::Cursor,
|
||||
}
|
||||
|
||||
impl Drop for CustomCursorInner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFreeCursor)(self.xconn.display, self.cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CustomCursorInner {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cursor == other.cursor
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CustomCursorInner {}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
SelectedCursor::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,14 @@ 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,4 +1,3 @@
|
||||
use std::{slice, str};
|
||||
use x11rb::protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
@@ -9,11 +8,6 @@ use super::*;
|
||||
pub const VIRTUAL_CORE_POINTER: u16 = 2;
|
||||
pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
|
||||
|
||||
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
|
||||
// re-allocate (and make another round-trip) in the *vast* majority of cases.
|
||||
// To test if `lookup_utf8` works correctly, set this to 1.
|
||||
const TEXT_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
impl XConnection {
|
||||
pub fn select_xinput_events(
|
||||
&self,
|
||||
@@ -60,52 +54,4 @@ impl XConnection {
|
||||
.reply()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn lookup_utf8_inner(
|
||||
&self,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: *mut u8,
|
||||
size: usize,
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = unsafe {
|
||||
(self.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer as *mut c_char,
|
||||
size as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
)
|
||||
};
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
// If the buffer overflows, we'll make a new one on the heap.
|
||||
let mut vec;
|
||||
|
||||
let (_, status, count) =
|
||||
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
|
||||
|
||||
let bytes = if status == ffi::XBufferOverflow {
|
||||
vec = Vec::with_capacity(count as usize);
|
||||
let (_, _, new_count) =
|
||||
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
|
||||
debug_assert_eq!(count, new_count);
|
||||
|
||||
unsafe { vec.set_len(count as usize) };
|
||||
&vec[..count as usize]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
|
||||
};
|
||||
|
||||
str::from_utf8(bytes).unwrap_or("").to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,6 @@ pub(crate) struct XSmartPointer<'a, T> {
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// Returns None if ptr is null.
|
||||
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
|
||||
if !ptr.is_null() {
|
||||
Some(XSmartPointer { xconn, ptr })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
// 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;
|
||||
@@ -16,14 +9,15 @@ 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::{
|
||||
geometry::*, hint::*, input::*, mouse::*, window_property::*, wm::*, xmodmap::ModifierKeymap,
|
||||
pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm::*};
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
os::raw::*,
|
||||
};
|
||||
|
||||
use super::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError};
|
||||
@@ -39,13 +33,6 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is impoartant, so pay attention!
|
||||
// Xlib has an output buffer, and tries to hide the async nature of X from you.
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
//! 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,22 +37,10 @@ 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("Xft.dpi", "")
|
||||
.and_then(|s| f64::from_str(s).ok())
|
||||
}
|
||||
|
||||
pub fn get_output_info(
|
||||
&self,
|
||||
resources: &monitor::ScreenResources,
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use bytemuck::{NoUninit, Pod};
|
||||
use std::sync::Arc;
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::errors::ReplyError;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub const CARDINAL_SIZE: usize = mem::size_of::<u32>();
|
||||
|
||||
pub type Cardinal = u32;
|
||||
pub const CARDINAL_SIZE: usize = mem::size_of::<u32>();
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GetPropertyError {
|
||||
@@ -20,6 +15,12 @@ pub enum GetPropertyError {
|
||||
FormatMismatch(c_int),
|
||||
}
|
||||
|
||||
impl<T: Into<ReplyError>> From<T> for GetPropertyError {
|
||||
fn from(e: T) -> Self {
|
||||
Self::X11rbError(Arc::new(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPropertyError {
|
||||
pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool {
|
||||
if let GetPropertyError::TypeMismatch(actual_type) = *self {
|
||||
@@ -30,24 +31,6 @@ impl GetPropertyError {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ReplyError>> From<T> for GetPropertyError {
|
||||
fn from(e: T) -> Self {
|
||||
Self::X11rbError(Arc::new(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for GetPropertyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
GetPropertyError::X11rbError(err) => err.fmt(f),
|
||||
GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"),
|
||||
GetPropertyError::FormatMismatch(err) => write!(f, "format mismatch: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for GetPropertyError {}
|
||||
|
||||
// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop.
|
||||
// To test if `get_property` works correctly, set this to 1.
|
||||
const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone!
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,15 @@ use std::{
|
||||
mem::replace,
|
||||
os::raw::*,
|
||||
path::Path,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
sync::{mpsc, Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use crate::cursor::CustomCursor as RootCustomCursor;
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
properties::{WmHints, WmSizeHints, WmSizeHintsSpecification},
|
||||
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
|
||||
protocol::{
|
||||
randr,
|
||||
shape::SK,
|
||||
@@ -33,14 +36,16 @@ use crate::{
|
||||
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
WindowAttributes, WindowButtons, WindowLevel,
|
||||
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
|
||||
WindowButtons, WindowLevel,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
ffi, util, CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
|
||||
XConnection,
|
||||
ffi,
|
||||
ime::ImeRequest,
|
||||
util::{self, CustomCursor, SelectedCursor},
|
||||
CookieResultExt, EventLoopWindowTarget, VoidCookie, WindowId, XConnection,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -118,7 +123,7 @@ impl SharedState {
|
||||
unsafe impl Send for UnownedWindow {}
|
||||
unsafe impl Sync for UnownedWindow {}
|
||||
|
||||
pub struct UnownedWindow {
|
||||
pub(crate) struct UnownedWindow {
|
||||
pub(crate) xconn: Arc<XConnection>, // never changes
|
||||
xwindow: xproto::Window, // never changes
|
||||
#[allow(dead_code)]
|
||||
@@ -126,11 +131,11 @@ pub struct UnownedWindow {
|
||||
root: xproto::Window, // never changes
|
||||
#[allow(dead_code)]
|
||||
screen_id: i32, // never changes
|
||||
cursor: Mutex<CursorIcon>,
|
||||
selected_cursor: Mutex<SelectedCursor>,
|
||||
cursor_grabbed_mode: Mutex<CursorGrabMode>,
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
cursor_visible: Mutex<bool>,
|
||||
ime_sender: Mutex<ImeSender>,
|
||||
ime_sender: Mutex<mpsc::Sender<ImeRequest>>,
|
||||
pub shared_state: Mutex<SharedState>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
activation_sender: WakeSender<super::ActivationToken>,
|
||||
@@ -355,7 +360,7 @@ impl UnownedWindow {
|
||||
visual,
|
||||
root,
|
||||
screen_id,
|
||||
cursor: Default::default(),
|
||||
selected_cursor: Default::default(),
|
||||
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
|
||||
cursor_visible: Mutex::new(true),
|
||||
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
|
||||
@@ -396,7 +401,7 @@ impl UnownedWindow {
|
||||
|
||||
// WM_CLASS must be set *before* mapping the window, as per ICCCM!
|
||||
{
|
||||
let (instance, class) = if let Some(name) = pl_attribs.name {
|
||||
let (class, instance) = if let Some(name) = pl_attribs.name {
|
||||
(name.instance, name.general)
|
||||
} else {
|
||||
let class = env::args_os()
|
||||
@@ -541,12 +546,11 @@ impl UnownedWindow {
|
||||
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
|
||||
.ignore_error();
|
||||
|
||||
// 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);
|
||||
{
|
||||
if let Some(ime) = event_loop.ime.as_ref() {
|
||||
let result = ime.borrow_mut().create_context(window.xwindow, false, None);
|
||||
leap!(result);
|
||||
}
|
||||
}
|
||||
|
||||
// These properties must be set after mapping
|
||||
@@ -987,7 +991,7 @@ impl UnownedWindow {
|
||||
xproto::EventMask::SUBSTRUCTURE_REDIRECT
|
||||
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
),
|
||||
[3u32, 0, 0, 0, 0],
|
||||
[WmHintsState::Iconic as u32, 0, 0, 0, 0],
|
||||
)
|
||||
} else {
|
||||
self.xconn.send_client_msg(
|
||||
@@ -1535,13 +1539,29 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let old_cursor = replace(&mut *self.cursor.lock().unwrap(), cursor);
|
||||
let old_cursor = replace(
|
||||
&mut *self.selected_cursor.lock().unwrap(),
|
||||
SelectedCursor::Named(cursor),
|
||||
);
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
if cursor != old_cursor && *self.cursor_visible.lock().unwrap() {
|
||||
if SelectedCursor::Named(cursor) != old_cursor && *self.cursor_visible.lock().unwrap() {
|
||||
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: RootCustomCursor) {
|
||||
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.inner) };
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
if *self.cursor_visible.lock().unwrap() {
|
||||
self.xconn.set_custom_cursor(self.xwindow, &new_cursor);
|
||||
}
|
||||
|
||||
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(new_cursor);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
|
||||
@@ -1628,13 +1648,23 @@ impl UnownedWindow {
|
||||
return;
|
||||
}
|
||||
let cursor = if visible {
|
||||
Some(*self.cursor.lock().unwrap())
|
||||
Some((*self.selected_cursor.lock().unwrap()).clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
*visible_lock = visible;
|
||||
drop(visible_lock);
|
||||
self.xconn.set_cursor_icon(self.xwindow, cursor);
|
||||
match cursor {
|
||||
Some(SelectedCursor::Custom(cursor)) => {
|
||||
self.xconn.set_custom_cursor(self.xwindow, &cursor);
|
||||
}
|
||||
Some(SelectedCursor::Named(cursor)) => {
|
||||
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
|
||||
}
|
||||
None => {
|
||||
self.xconn.set_cursor_icon(self.xwindow, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1761,11 +1791,11 @@ impl UnownedWindow {
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) {
|
||||
let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
|
||||
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position(
|
||||
self.xwindow as ffi::Window,
|
||||
x,
|
||||
y,
|
||||
));
|
||||
let _ = self
|
||||
.ime_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(ImeRequest::Position(self.xwindow, x, y));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1774,7 +1804,7 @@ impl UnownedWindow {
|
||||
.ime_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed));
|
||||
.send(ImeRequest::Allow(self.xwindow, allowed));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -4,25 +4,17 @@ use std::{
|
||||
fmt, ptr,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc, Mutex, RwLock, RwLockReadGuard,
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
protocol::{
|
||||
randr::ConnectionExt as _,
|
||||
xproto::{self, ConnectionExt},
|
||||
},
|
||||
resource_manager,
|
||||
xcb_ffi::XCBConnection,
|
||||
};
|
||||
use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
|
||||
|
||||
/// A connection to an X server.
|
||||
pub struct XConnection {
|
||||
pub(crate) struct XConnection {
|
||||
pub xlib: ffi::Xlib,
|
||||
pub xcursor: ffi::Xcursor,
|
||||
|
||||
@@ -53,13 +45,7 @@ pub struct XConnection {
|
||||
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
|
||||
|
||||
/// The resource database.
|
||||
database: RwLock<resource_manager::Database>,
|
||||
|
||||
/// RandR version.
|
||||
randr_version: (u32, u32),
|
||||
|
||||
/// Atom for the XSettings screen.
|
||||
xsettings_screen: Option<xproto::Atom>,
|
||||
database: resource_manager::Database,
|
||||
|
||||
pub latest_error: Mutex<Option<XError>>,
|
||||
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
|
||||
@@ -105,31 +91,27 @@ impl XConnection {
|
||||
conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))?
|
||||
};
|
||||
|
||||
// Make sure Xlib knows XCB is handling events.
|
||||
unsafe {
|
||||
(xlib_xcb.XSetEventQueueOwner)(
|
||||
display,
|
||||
x11_dl::xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the default screen.
|
||||
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
|
||||
|
||||
// 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.
|
||||
// Fetch the 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,
|
||||
@@ -141,44 +123,11 @@ impl XConnection {
|
||||
timestamp: AtomicU32::new(0),
|
||||
latest_error: Mutex::new(None),
|
||||
monitor_handles: Mutex::new(None),
|
||||
database: RwLock::new(database),
|
||||
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> {
|
||||
@@ -190,11 +139,6 @@ 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 {
|
||||
@@ -223,16 +167,8 @@ impl XConnection {
|
||||
|
||||
/// Get the resource database.
|
||||
#[inline]
|
||||
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(())
|
||||
pub fn database(&self) -> &resource_manager::Database {
|
||||
&self.database
|
||||
}
|
||||
|
||||
/// Get the latest timestamp.
|
||||
@@ -264,12 +200,6 @@ impl XConnection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the atom for Xsettings.
|
||||
#[inline]
|
||||
pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
|
||||
self.xsettings_screen
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for XConnection {
|
||||
|
||||
@@ -1,343 +0,0 @@
|
||||
//! 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],
|
||||
)?;
|
||||
|
||||
// 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,10 +209,6 @@ 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)
|
||||
}
|
||||
@@ -438,10 +434,6 @@ impl AppState {
|
||||
HANDLER.exit()
|
||||
}
|
||||
|
||||
pub fn clear_exit() {
|
||||
HANDLER.clear_exit()
|
||||
}
|
||||
|
||||
pub fn exiting() -> bool {
|
||||
HANDLER.exiting()
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ extern_methods!(
|
||||
#[method(setMainMenu:)]
|
||||
pub fn setMainMenu(&self, menu: &NSMenu);
|
||||
|
||||
#[method(setServicesMenu:)]
|
||||
pub fn setServicesMenu(&self, menu: &NSMenu);
|
||||
|
||||
#[method_id(effectiveAppearance)]
|
||||
pub fn effectiveAppearance(&self) -> Id<NSAppearance>;
|
||||
|
||||
|
||||
56
src/platform_impl/macos/appkit/bitmap_image_rep.rs
Normal file
56
src/platform_impl/macos/appkit/bitmap_image_rep.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::ffi::c_uchar;
|
||||
|
||||
use icrate::Foundation::{NSInteger, NSObject, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::Bool;
|
||||
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NSImageRep;
|
||||
|
||||
unsafe impl ClassType for NSImageRep {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern "C" {
|
||||
static NSDeviceRGBColorSpace: &'static NSString;
|
||||
}
|
||||
|
||||
extern_class!(
|
||||
// <https://developer.apple.com/documentation/appkit/nsbitmapimagerep?language=objc>
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSBitmapImageRep;
|
||||
|
||||
unsafe impl ClassType for NSBitmapImageRep {
|
||||
type Super = NSImageRep;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSBitmapImageRep {
|
||||
pub fn init_rgba(width: NSInteger, height: NSInteger) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![Self::alloc(),
|
||||
initWithBitmapDataPlanes: std::ptr::null_mut::<*mut c_uchar>(),
|
||||
pixelsWide: width,
|
||||
pixelsHigh: height,
|
||||
bitsPerSample: 8 as NSInteger,
|
||||
samplesPerPixel: 4 as NSInteger,
|
||||
hasAlpha: Bool::new(true),
|
||||
isPlanar: Bool::new(false),
|
||||
colorSpaceName: NSDeviceRGBColorSpace,
|
||||
bytesPerRow: width * 4,
|
||||
bitsPerPixel: 32 as NSInteger,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitmap_data(&self) -> *mut u8 {
|
||||
unsafe { msg_send![self, bitmapData] }
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -2,13 +2,14 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use icrate::ns_string;
|
||||
use icrate::Foundation::{
|
||||
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSString,
|
||||
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString,
|
||||
};
|
||||
use objc2::rc::{DefaultId, Id};
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType};
|
||||
|
||||
use super::NSImage;
|
||||
use super::{NSBitmapImageRep, NSImage};
|
||||
use crate::cursor::CursorImage;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
extern_class!(
|
||||
@@ -232,6 +233,23 @@ impl NSCursor {
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_image(cursor: &CursorImage) -> Id<Self> {
|
||||
let width = cursor.width;
|
||||
let height = cursor.height;
|
||||
|
||||
let bitmap = NSBitmapImageRep::init_rgba(width as isize, height as isize);
|
||||
let bitmap_data =
|
||||
unsafe { std::slice::from_raw_parts_mut(bitmap.bitmap_data(), cursor.rgba.len()) };
|
||||
bitmap_data.copy_from_slice(&cursor.rgba);
|
||||
|
||||
let image = NSImage::init_with_size(NSSize::new(width.into(), height.into()));
|
||||
image.add_representation(&bitmap);
|
||||
|
||||
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
|
||||
|
||||
NSCursor::new(&image, hotspot)
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultId for NSCursor {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use icrate::Foundation::{NSData, NSObject, NSString};
|
||||
use icrate::Foundation::{NSData, NSObject, NSSize, NSString};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
|
||||
|
||||
use super::NSBitmapImageRep;
|
||||
|
||||
extern_class!(
|
||||
// TODO: Can this be mutable?
|
||||
@@ -32,5 +34,13 @@ extern_methods!(
|
||||
pub fn new_with_data(data: &NSData) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initWithData: data] }
|
||||
}
|
||||
|
||||
pub fn init_with_size(size: NSSize) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::alloc(), initWithSize: size] }
|
||||
}
|
||||
|
||||
pub fn add_representation(&self, representation: &NSBitmapImageRep) {
|
||||
unsafe { msg_send![self, addRepresentation: representation] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -20,7 +20,11 @@ extern_methods!(
|
||||
#[method_id(new)]
|
||||
pub fn new() -> Id<Self>;
|
||||
|
||||
pub fn newWithTitle(title: &NSString, action: Sel, key_equivalent: &NSString) -> Id<Self> {
|
||||
pub fn newWithTitle(
|
||||
title: &NSString,
|
||||
action: Option<Sel>,
|
||||
key_equivalent: &NSString,
|
||||
) -> Id<Self> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::alloc(),
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
mod appearance;
|
||||
mod application;
|
||||
mod bitmap_image_rep;
|
||||
mod button;
|
||||
mod color;
|
||||
mod control;
|
||||
@@ -36,6 +37,7 @@ pub(crate) use self::application::{
|
||||
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
|
||||
NSRequestUserAttentionType,
|
||||
};
|
||||
pub(crate) use self::bitmap_image_rep::NSBitmapImageRep;
|
||||
pub(crate) use self::button::NSButton;
|
||||
pub(crate) use self::color::NSColor;
|
||||
pub(crate) use self::control::NSControl;
|
||||
|
||||
@@ -39,7 +39,6 @@ impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ignores ALL modifiers.
|
||||
pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
let mut string = [0; 16];
|
||||
let input_source;
|
||||
@@ -98,7 +97,6 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
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()
|
||||
@@ -128,13 +126,6 @@ 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 {
|
||||
@@ -155,29 +146,21 @@ 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 and cmd here, not checking for alt because we DO want to
|
||||
// Only checking for ctrl 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 && !has_cmd => {
|
||||
// Character heeding both SHIFT and ALT.
|
||||
Key::Character(text.clone())
|
||||
}
|
||||
|
||||
Some(text) if !has_ctrl => 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.
|
||||
// Don't try to get text for events which likely don't have it.
|
||||
_ => key_without_modifiers.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -116,10 +116,6 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
AppState::exit()
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
AppState::clear_exit()
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
AppState::exiting()
|
||||
}
|
||||
|
||||
@@ -21,7 +21,16 @@ pub fn initialize() {
|
||||
|
||||
// About menu item
|
||||
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
|
||||
let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None);
|
||||
let about_item = menu_item(
|
||||
&about_item_title,
|
||||
Some(sel!(orderFrontStandardAboutPanel:)),
|
||||
None,
|
||||
);
|
||||
|
||||
// Services menu item
|
||||
let services_menu = NSMenu::new();
|
||||
let services_item = menu_item(ns_string!("Services"), None, None);
|
||||
services_item.setSubmenu(&services_menu);
|
||||
|
||||
// Seperator menu item
|
||||
let sep_first = NSMenuItem::separatorItem();
|
||||
@@ -30,7 +39,7 @@ pub fn initialize() {
|
||||
let hide_item_title = ns_string!("Hide ").stringByAppendingString(&process_name);
|
||||
let hide_item = menu_item(
|
||||
&hide_item_title,
|
||||
sel!(hide:),
|
||||
Some(sel!(hide:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: None,
|
||||
@@ -41,7 +50,7 @@ pub fn initialize() {
|
||||
let hide_others_item_title = ns_string!("Hide Others");
|
||||
let hide_others_item = menu_item(
|
||||
hide_others_item_title,
|
||||
sel!(hideOtherApplications:),
|
||||
Some(sel!(hideOtherApplications:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(
|
||||
@@ -52,7 +61,11 @@ pub fn initialize() {
|
||||
|
||||
// Show applications menu item
|
||||
let show_all_item_title = ns_string!("Show All");
|
||||
let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None);
|
||||
let show_all_item = menu_item(
|
||||
show_all_item_title,
|
||||
Some(sel!(unhideAllApplications:)),
|
||||
None,
|
||||
);
|
||||
|
||||
// Seperator menu item
|
||||
let sep = NSMenuItem::separatorItem();
|
||||
@@ -61,7 +74,7 @@ pub fn initialize() {
|
||||
let quit_item_title = ns_string!("Quit ").stringByAppendingString(&process_name);
|
||||
let quit_item = menu_item(
|
||||
&quit_item_title,
|
||||
sel!(terminate:),
|
||||
Some(sel!(terminate:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("q"),
|
||||
masks: None,
|
||||
@@ -70,6 +83,7 @@ pub fn initialize() {
|
||||
|
||||
app_menu.addItem(&about_item);
|
||||
app_menu.addItem(&sep_first);
|
||||
app_menu.addItem(&services_item);
|
||||
app_menu.addItem(&hide_item);
|
||||
app_menu.addItem(&hide_others_item);
|
||||
app_menu.addItem(&show_all_item);
|
||||
@@ -78,12 +92,13 @@ pub fn initialize() {
|
||||
app_menu_item.setSubmenu(&app_menu);
|
||||
|
||||
let app = NSApp();
|
||||
app.setServicesMenu(&services_menu);
|
||||
app.setMainMenu(&menubar);
|
||||
}
|
||||
|
||||
fn menu_item(
|
||||
title: &NSString,
|
||||
selector: Sel,
|
||||
selector: Option<Sel>,
|
||||
key_equivalent: Option<KeyEquivalent<'_>>,
|
||||
) -> Id<NSMenuItem> {
|
||||
let (key, masks) = match key_equivalent {
|
||||
|
||||
@@ -28,6 +28,7 @@ pub(crate) use self::{
|
||||
use crate::event::DeviceId as RootDeviceId;
|
||||
|
||||
pub(crate) use self::window::Window;
|
||||
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
self,
|
||||
ffi::c_void,
|
||||
panic::{AssertUnwindSafe, UnwindSafe},
|
||||
ptr,
|
||||
|
||||
@@ -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 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;
|
||||
let y = base_y - self.state.ime_position.get().y;
|
||||
let LogicalSize { width, height } = self.state.ime_size.get();
|
||||
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height))
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::os::raw::c_void;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::{
|
||||
dpi::{
|
||||
LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical,
|
||||
@@ -834,6 +835,13 @@ impl WinitWindow {
|
||||
self.invalidateCursorRectsForView(&view);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
let view = self.view();
|
||||
view.set_cursor_icon(NSCursor::from_image(&cursor.inner));
|
||||
self.invalidateCursorRectsForView(&view);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
let associate_mouse_cursor = match mode {
|
||||
|
||||
@@ -8,18 +8,17 @@ use std::{
|
||||
};
|
||||
|
||||
use orbclient::{
|
||||
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent,
|
||||
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
|
||||
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, 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, NamedKey, NativeKey,
|
||||
NativeKeyCode, PhysicalKey,
|
||||
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
|
||||
PhysicalKey,
|
||||
},
|
||||
window::WindowId as RootWindowId,
|
||||
};
|
||||
@@ -29,107 +28,90 @@ use super::{
|
||||
RedoxSocket, TimeSocket, WindowId, WindowProperties,
|
||||
};
|
||||
|
||||
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),
|
||||
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,
|
||||
|
||||
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)),
|
||||
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,
|
||||
|
||||
_ => return (PhysicalKey::Unidentified(NativeKeyCode::Unidentified), None),
|
||||
};
|
||||
(PhysicalKey::Code(key_code), named_key_opt)
|
||||
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
|
||||
})
|
||||
}
|
||||
|
||||
fn element_state(pressed: bool) -> event::ElementState {
|
||||
@@ -171,22 +153,6 @@ 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,
|
||||
@@ -367,75 +333,39 @@ impl<T: 'static> EventLoop<T> {
|
||||
{
|
||||
match event_option {
|
||||
EventOption::Key(KeyEvent {
|
||||
character,
|
||||
character: _,
|
||||
scancode,
|
||||
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 {
|
||||
if scancode != 0 {
|
||||
let physical_key = convert_scancode(scancode);
|
||||
let modifiers_before = event_state.keyboard;
|
||||
event_state.key(physical_key, pressed);
|
||||
event_handler(event::Event::WindowEvent {
|
||||
window_id: RootWindowId(window_id),
|
||||
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
|
||||
})
|
||||
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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
EventOption::TextInput(TextInputEvent { character }) => {
|
||||
@@ -457,14 +387,6 @@ 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,
|
||||
@@ -518,7 +440,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Acknowledge resize after event loop.
|
||||
event_state.resize_opt = Some((width, height));
|
||||
}
|
||||
//TODO: Screen, Clipboard, Drop
|
||||
//TODO: Clipboard
|
||||
EventOption::Hover(HoverEvent { entered }) => {
|
||||
if entered {
|
||||
event_handler(event::Event::WindowEvent {
|
||||
|
||||
@@ -4,12 +4,7 @@ use std::fmt::{self, Display, Formatter};
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
keyboard::Key,
|
||||
};
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
|
||||
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
mod event_loop;
|
||||
@@ -198,6 +193,7 @@ impl Display for OsError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
@@ -264,8 +260,5 @@ impl VideoMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub key_without_modifiers: Key,
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventExtra {}
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cursor::CustomCursor,
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
platform_impl::Fullscreen,
|
||||
@@ -12,8 +13,8 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
EventLoopWindowTarget, MonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
|
||||
RedoxSocket, TimeSocket, WindowId, WindowProperties,
|
||||
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
|
||||
TimeSocket, WindowId, WindowProperties,
|
||||
};
|
||||
|
||||
// These values match the values uses in the `window_new` function in orbital:
|
||||
@@ -21,9 +22,7 @@ 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';
|
||||
|
||||
@@ -60,15 +59,11 @@ 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: fullscreen
|
||||
//TODO: maximized, fullscreen, visible
|
||||
|
||||
if attrs.transparent {
|
||||
flag_str.push(ORBITAL_FLAG_TRANSPARENT);
|
||||
@@ -78,10 +73,6 @@ 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);
|
||||
@@ -140,23 +131,6 @@ 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 {
|
||||
@@ -282,21 +256,17 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_TRANSPARENT, transparent);
|
||||
}
|
||||
pub fn set_transparent(&self, _transparent: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_blur(&self, _blur: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_HIDDEN, !visible);
|
||||
}
|
||||
pub fn set_visible(&self, _visibility: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false))
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -308,13 +278,17 @@ impl Window {
|
||||
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizeable: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_RESIZABLE, resizeable);
|
||||
}
|
||||
pub fn set_resizable(&self, _resizeable: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn is_resizable(&self) -> bool {
|
||||
self.get_flag(ORBITAL_FLAG_RESIZABLE).unwrap_or(false)
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -326,13 +300,11 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_MAXIMIZED, maximized);
|
||||
}
|
||||
pub fn set_maximized(&self, _maximized: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
self.get_flag(ORBITAL_FLAG_MAXIMIZED).unwrap_or(false)
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -344,30 +316,21 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let _ = self.set_flag(ORBITAL_FLAG_BORDERLESS, !decorations);
|
||||
}
|
||||
pub fn set_decorations(&self, _decorations: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&self) -> bool {
|
||||
!self.get_flag(ORBITAL_FLAG_BORDERLESS).unwrap_or(false)
|
||||
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)
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn set_window_level(&self, _level: window::WindowLevel) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
|
||||
@@ -390,6 +353,8 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
||||
|
||||
pub fn set_custom_cursor(&self, _: CustomCursor) {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
@@ -398,58 +363,30 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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(())
|
||||
pub fn set_cursor_grab(&self, _: window::CursorGrabMode) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let _ = self
|
||||
.window_socket
|
||||
.write(format!("M,C,{}", if visible { 1 } else { 0 }).as_bytes());
|
||||
}
|
||||
pub fn set_cursor_visible(&self, _: bool) {}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
|
||||
self.window_socket
|
||||
.write(b"D")
|
||||
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
|
||||
Ok(())
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_resize_window(
|
||||
&self,
|
||||
direction: window::ResizeDirection,
|
||||
_direction: window::ResizeDirection,
|
||||
) -> Result<(), error::ExternalError> {
|
||||
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(())
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
364
src/platform_impl/web/cursor.rs
Normal file
364
src/platform_impl/web/cursor.rs
Normal file
@@ -0,0 +1,364 @@
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
ops::Deref,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
use crate::cursor::{BadImage, CursorImage};
|
||||
use cursor_icon::CursorIcon;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{
|
||||
Blob, Document, HtmlCanvasElement, ImageBitmap, ImageBitmapOptions,
|
||||
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
||||
};
|
||||
|
||||
use super::backend::Style;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum WebCustomCursor {
|
||||
Image(CursorImage),
|
||||
Url {
|
||||
url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
}
|
||||
|
||||
impl WebCustomCursor {
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
) -> Result<Self, BadImage> {
|
||||
Ok(Self::Image(CursorImage::from_rgba(
|
||||
rgba, width, height, hotspot_x, hotspot_y,
|
||||
)?))
|
||||
}
|
||||
|
||||
pub(super) fn build(
|
||||
&self,
|
||||
window: &Window,
|
||||
document: &Document,
|
||||
style: &Style,
|
||||
previous: SelectedCursor,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
) -> SelectedCursor {
|
||||
let previous = previous.into();
|
||||
|
||||
match self {
|
||||
WebCustomCursor::Image(image) => SelectedCursor::Image(CursorImageState::from_image(
|
||||
window,
|
||||
document.clone(),
|
||||
style.clone(),
|
||||
image,
|
||||
previous,
|
||||
cursor_visible,
|
||||
)),
|
||||
WebCustomCursor::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
} => {
|
||||
let value = previous.style_with_url(url, *hotspot_x, *hotspot_y);
|
||||
|
||||
if cursor_visible.get() {
|
||||
style.set("cursor", &value);
|
||||
}
|
||||
|
||||
SelectedCursor::Url {
|
||||
style: value,
|
||||
previous,
|
||||
url: url.clone(),
|
||||
hotspot_x: *hotspot_x,
|
||||
hotspot_y: *hotspot_y,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SelectedCursor {
|
||||
Named(CursorIcon),
|
||||
Url {
|
||||
style: String,
|
||||
previous: Previous,
|
||||
url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
Image(Rc<RefCell<Option<CursorImageState>>>),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
Self::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedCursor {
|
||||
pub fn set_style(&self, style: &Style) {
|
||||
let value = match self {
|
||||
SelectedCursor::Named(icon) => icon.name(),
|
||||
SelectedCursor::Url { style, .. } => style,
|
||||
SelectedCursor::Image(image) => {
|
||||
let image = image.borrow();
|
||||
let value = match image.deref().as_ref().unwrap() {
|
||||
CursorImageState::Loading { previous, .. } => previous.style(),
|
||||
CursorImageState::Failed(previous) => previous.style(),
|
||||
CursorImageState::Ready { style, .. } => style,
|
||||
};
|
||||
return style.set("cursor", value);
|
||||
}
|
||||
};
|
||||
|
||||
style.set("cursor", value);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Previous {
|
||||
Named(CursorIcon),
|
||||
Url {
|
||||
style: String,
|
||||
url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
Image {
|
||||
style: String,
|
||||
image: WebCursorImage,
|
||||
},
|
||||
}
|
||||
|
||||
impl Previous {
|
||||
fn style(&self) -> &str {
|
||||
match self {
|
||||
Previous::Named(icon) => icon.name(),
|
||||
Previous::Url { style: url, .. } => url,
|
||||
Previous::Image { style, .. } => style,
|
||||
}
|
||||
}
|
||||
|
||||
fn style_with_url(&self, new_url: &str, new_hotspot_x: u16, new_hotspot_y: u16) -> String {
|
||||
match self {
|
||||
Previous::Named(icon) => format!("url({new_url}) {new_hotspot_x} {new_hotspot_y}, {}", icon.name()),
|
||||
Previous::Url {
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
}
|
||||
| Previous::Image {
|
||||
image:
|
||||
WebCursorImage {
|
||||
data_url: url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => format!(
|
||||
"url({new_url}) {new_hotspot_x} {new_hotspot_y}, url({url}) {hotspot_x} {hotspot_y}, auto",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SelectedCursor> for Previous {
|
||||
fn from(value: SelectedCursor) -> Self {
|
||||
match value {
|
||||
SelectedCursor::Named(icon) => Self::Named(icon),
|
||||
SelectedCursor::Url {
|
||||
style,
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
..
|
||||
} => Self::Url {
|
||||
style,
|
||||
url,
|
||||
hotspot_x,
|
||||
hotspot_y,
|
||||
},
|
||||
SelectedCursor::Image(image) => {
|
||||
match Rc::try_unwrap(image).unwrap().into_inner().unwrap() {
|
||||
CursorImageState::Loading { previous, .. } => previous,
|
||||
CursorImageState::Failed(previous) => previous,
|
||||
CursorImageState::Ready {
|
||||
style,
|
||||
image: current,
|
||||
..
|
||||
} => Self::Image {
|
||||
style,
|
||||
image: current,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CursorImageState {
|
||||
Loading {
|
||||
style: Style,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
previous: Previous,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
},
|
||||
Failed(Previous),
|
||||
Ready {
|
||||
style: String,
|
||||
image: WebCursorImage,
|
||||
previous: Previous,
|
||||
},
|
||||
}
|
||||
|
||||
impl CursorImageState {
|
||||
fn from_image(
|
||||
window: &Window,
|
||||
document: Document,
|
||||
style: Style,
|
||||
image: &CursorImage,
|
||||
previous: Previous,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
) -> Rc<RefCell<Option<Self>>> {
|
||||
// Can't create array directly when backed by SharedArrayBuffer.
|
||||
// Adapted from https://github.com/rust-windowing/softbuffer/blob/ab7688e2ed2e2eca51b3c4e1863a5bd7fe85800e/src/web.rs#L196-L223
|
||||
#[cfg(target_feature = "atomics")]
|
||||
let image_data = {
|
||||
use js_sys::{Uint8Array, Uint8ClampedArray};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = ImageData)]
|
||||
type ImageDataExt;
|
||||
#[wasm_bindgen(catch, constructor, js_class = ImageData)]
|
||||
fn new(array: Uint8ClampedArray, sw: u32) -> Result<ImageDataExt, JsValue>;
|
||||
}
|
||||
|
||||
let array = Uint8Array::new_with_length(image.rgba.len() as u32);
|
||||
array.copy_from(&image.rgba);
|
||||
let array = Uint8ClampedArray::new(&array);
|
||||
ImageDataExt::new(array, image.width as u32)
|
||||
.map(JsValue::from)
|
||||
.map(ImageData::unchecked_from_js)
|
||||
.unwrap()
|
||||
};
|
||||
#[cfg(not(target_feature = "atomics"))]
|
||||
let image_data = ImageData::new_with_u8_clamped_array(
|
||||
wasm_bindgen::Clamped(&image.rgba),
|
||||
image.width as u32,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut options = ImageBitmapOptions::new();
|
||||
options.premultiply_alpha(PremultiplyAlpha::None);
|
||||
let bitmap = JsFuture::from(
|
||||
window
|
||||
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let state = Rc::new(RefCell::new(Some(Self::Loading {
|
||||
style,
|
||||
cursor_visible,
|
||||
previous,
|
||||
hotspot_x: image.hotspot_x,
|
||||
hotspot_y: image.hotspot_y,
|
||||
})));
|
||||
|
||||
wasm_bindgen_futures::spawn_local({
|
||||
let weak = Rc::downgrade(&state);
|
||||
let CursorImage { width, height, .. } = *image;
|
||||
async move {
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let bitmap: ImageBitmap = bitmap.await.unwrap().unchecked_into();
|
||||
|
||||
if weak.strong_count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas: HtmlCanvasElement =
|
||||
document.create_element("canvas").unwrap().unchecked_into();
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_width(width as u32);
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
canvas.set_height(height as u32);
|
||||
|
||||
let context: ImageBitmapRenderingContext = canvas
|
||||
.get_context("bitmaprenderer")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.unchecked_into();
|
||||
context.transfer_from_image_bitmap(&bitmap);
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_STATE: RefCell<Option<Weak<RefCell<Option<CursorImageState>>>>> = RefCell::new(None);
|
||||
// `HTMLCanvasElement.toBlob()` can't be interrupted. So we have to use a
|
||||
// `Closure` that doesn't need to be garbage-collected.
|
||||
static CALLBACK: Closure<dyn Fn(Option<Blob>)> = Closure::new(|blob| {
|
||||
CURRENT_STATE.with(|weak| {
|
||||
let Some(state) = weak.borrow_mut().take().and_then(|weak| weak.upgrade()) else {
|
||||
return;
|
||||
};
|
||||
let mut state = state.borrow_mut();
|
||||
// Extract old state.
|
||||
let CursorImageState::Loading { style, cursor_visible, previous, hotspot_x, hotspot_y, .. } = state.take().unwrap() else {
|
||||
unreachable!("found invalid state")
|
||||
};
|
||||
|
||||
let Some(blob) = blob else {
|
||||
*state = Some(CursorImageState::Failed(previous));
|
||||
return;
|
||||
};
|
||||
let data_url = Url::create_object_url_with_blob(&blob).unwrap();
|
||||
|
||||
let value = previous.style_with_url(&data_url, hotspot_x, hotspot_y);
|
||||
|
||||
if cursor_visible.get() {
|
||||
style.set("cursor", &value);
|
||||
}
|
||||
|
||||
*state = Some(
|
||||
CursorImageState::Ready {
|
||||
style: value,
|
||||
image: WebCursorImage{ data_url, hotspot_x, hotspot_y },
|
||||
previous,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
CURRENT_STATE.with(|state| *state.borrow_mut() = Some(weak));
|
||||
CALLBACK
|
||||
.with(|callback| canvas.to_blob(callback.as_ref().unchecked_ref()).unwrap());
|
||||
}
|
||||
});
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebCursorImage {
|
||||
data_url: String,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
}
|
||||
|
||||
impl Drop for WebCursorImage {
|
||||
fn drop(&mut self) {
|
||||
Url::revoke_object_url(&self.data_url).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use crate::window::WindowId;
|
||||
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
clone::Clone,
|
||||
collections::{HashSet, VecDeque},
|
||||
iter,
|
||||
ops::Deref,
|
||||
@@ -66,7 +67,6 @@ pub struct Execution {
|
||||
on_key_press: OnEventHandle<KeyboardEvent>,
|
||||
on_key_release: OnEventHandle<KeyboardEvent>,
|
||||
on_visibility_change: OnEventHandle<web_sys::Event>,
|
||||
on_touch_end: OnEventHandle<web_sys::Event>,
|
||||
}
|
||||
|
||||
enum RunnerEnum {
|
||||
@@ -180,7 +180,6 @@ impl Shared {
|
||||
on_key_press: RefCell::new(None),
|
||||
on_key_release: RefCell::new(None),
|
||||
on_visibility_change: RefCell::new(None),
|
||||
on_touch_end: RefCell::new(None),
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -341,8 +340,6 @@ impl Shared {
|
||||
self.window().clone(),
|
||||
"pointerdown",
|
||||
Closure::new(move |event: PointerEvent| {
|
||||
runner.transient_activation();
|
||||
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
@@ -366,8 +363,6 @@ impl Shared {
|
||||
self.window().clone(),
|
||||
"pointerup",
|
||||
Closure::new(move |event: PointerEvent| {
|
||||
runner.transient_activation();
|
||||
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
@@ -391,8 +386,6 @@ impl Shared {
|
||||
self.window().clone(),
|
||||
"keydown",
|
||||
Closure::new(move |event: KeyboardEvent| {
|
||||
runner.transient_activation();
|
||||
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
@@ -451,14 +444,6 @@ impl Shared {
|
||||
}
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
*self.0.on_touch_end.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"touchend",
|
||||
Closure::new(move |_| {
|
||||
runner.transient_activation();
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate a strictly increasing ID
|
||||
@@ -787,18 +772,6 @@ impl Shared {
|
||||
}
|
||||
}
|
||||
|
||||
fn transient_activation(&self) {
|
||||
self.0
|
||||
.all_canvases
|
||||
.borrow()
|
||||
.iter()
|
||||
.for_each(|(_, canvas, _)| {
|
||||
if let Some(canvas) = canvas.upgrade() {
|
||||
canvas.borrow().transient_activation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn event_loop_recreation(&self, allow: bool) {
|
||||
self.0.event_loop_recreation.set(allow)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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},
|
||||
@@ -123,25 +122,6 @@ 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(
|
||||
@@ -667,10 +647,6 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
|
||||
let runner = self.runner.clone();
|
||||
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,10 +17,8 @@
|
||||
// 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 cursor;
|
||||
mod device;
|
||||
mod error;
|
||||
mod event_loop;
|
||||
@@ -42,3 +40,4 @@ pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId
|
||||
pub(crate) use self::keyboard::KeyEventExtra;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
pub(crate) use cursor::WebCustomCursor as PlatformCustomCursor;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
|
||||
PointerEvent, WheelEvent,
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent,
|
||||
};
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
@@ -19,11 +18,10 @@ use crate::window::{WindowAttributes, WindowId as RootWindowId};
|
||||
use super::super::WindowId;
|
||||
use super::animation_frame::AnimationFrameHandler;
|
||||
use super::event_handle::EventListenerHandle;
|
||||
use super::fullscreen::FullscreenHandler;
|
||||
use super::intersection_handle::IntersectionObserverHandle;
|
||||
use super::media_query_handle::MediaQueryListHandle;
|
||||
use super::pointer::PointerHandler;
|
||||
use super::{event, ButtonsState, ResizeScaleHandle};
|
||||
use super::{event, fullscreen, ButtonsState, ResizeScaleHandle};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Canvas {
|
||||
@@ -43,7 +41,6 @@ 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 {
|
||||
@@ -51,10 +48,15 @@ pub struct Common {
|
||||
pub document: Document,
|
||||
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
||||
pub raw: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
style: Style,
|
||||
old_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
current_size: Rc<Cell<PhysicalSize<u32>>>,
|
||||
fullscreen_handler: Rc<FullscreenHandler>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Style {
|
||||
read: CssStyleDeclaration,
|
||||
write: CssStyleDeclaration,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
@@ -92,12 +94,7 @@ impl Canvas {
|
||||
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let style = window
|
||||
.get_computed_style(&canvas)
|
||||
.expect("Failed to obtain computed style")
|
||||
// this can't fail: we aren't using a pseudo-element
|
||||
.expect("Invalid pseudo-element");
|
||||
let style = Style::new(&window, &canvas);
|
||||
|
||||
let common = Common {
|
||||
window: window.clone(),
|
||||
@@ -106,7 +103,6 @@ impl Canvas {
|
||||
style,
|
||||
old_size: Rc::default(),
|
||||
current_size: Rc::default(),
|
||||
fullscreen_handler: Rc::new(FullscreenHandler::new(document.clone(), canvas.clone())),
|
||||
};
|
||||
|
||||
if let Some(size) = attr.inner_size {
|
||||
@@ -130,7 +126,7 @@ impl Canvas {
|
||||
}
|
||||
|
||||
if attr.fullscreen.0.is_some() {
|
||||
common.fullscreen_handler.request_fullscreen();
|
||||
fullscreen::request_fullscreen(&document, &canvas);
|
||||
}
|
||||
|
||||
if attr.active {
|
||||
@@ -154,7 +150,6 @@ impl Canvas {
|
||||
on_intersect: None,
|
||||
animation_frame_handler: AnimationFrameHandler::new(window),
|
||||
on_touch_end: None,
|
||||
on_context_menu: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -181,9 +176,7 @@ impl Canvas {
|
||||
y: bounds.y(),
|
||||
};
|
||||
|
||||
if self.document().contains(Some(self.raw()))
|
||||
&& self.style().get_property_value("display").unwrap() != "none"
|
||||
{
|
||||
if self.document().contains(Some(self.raw())) && self.style().get("display") != "none" {
|
||||
position.x += super::style_size_property(self.style(), "border-left-width")
|
||||
+ super::style_size_property(self.style(), "padding-left");
|
||||
position.y += super::style_size_property(self.style(), "border-top-width")
|
||||
@@ -229,7 +222,7 @@ impl Canvas {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn style(&self) -> &CssStyleDeclaration {
|
||||
pub fn style(&self) -> &Style {
|
||||
&self.common.style
|
||||
}
|
||||
|
||||
@@ -285,7 +278,7 @@ impl Canvas {
|
||||
where
|
||||
F: 'static + FnMut(PhysicalKey, Key, Option<SmolStr>, KeyLocation, bool, ModifiersState),
|
||||
{
|
||||
self.on_keyboard_press = Some(self.common.add_transient_event(
|
||||
self.on_keyboard_press = Some(self.common.add_event(
|
||||
"keydown",
|
||||
move |event: KeyboardEvent| {
|
||||
if prevent_default {
|
||||
@@ -445,31 +438,16 @@ impl Canvas {
|
||||
self.animation_frame_handler.on_animation_frame(f)
|
||||
}
|
||||
|
||||
pub(crate) fn on_touch_end(&mut self) {
|
||||
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()
|
||||
fullscreen::request_fullscreen(self.document(), self.raw());
|
||||
}
|
||||
|
||||
pub fn exit_fullscreen(&self) {
|
||||
self.common.fullscreen_handler.exit_fullscreen()
|
||||
fullscreen::exit_fullscreen(self.document(), self.raw());
|
||||
}
|
||||
|
||||
pub fn is_fullscreen(&self) -> bool {
|
||||
self.common.fullscreen_handler.is_fullscreen()
|
||||
fullscreen::is_fullscreen(self.document(), self.raw())
|
||||
}
|
||||
|
||||
pub fn request_animation_frame(&self) {
|
||||
@@ -520,10 +498,6 @@ impl Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn transient_activation(&self) {
|
||||
self.common.fullscreen_handler.transient_activation()
|
||||
}
|
||||
|
||||
pub fn remove_listeners(&mut self) {
|
||||
self.on_touch_start = None;
|
||||
self.on_focus = None;
|
||||
@@ -537,8 +511,6 @@ impl Canvas {
|
||||
self.on_intersect = None;
|
||||
self.animation_frame_handler.cancel();
|
||||
self.on_touch_end = None;
|
||||
self.common.fullscreen_handler.cancel();
|
||||
self.on_context_menu = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,27 +526,38 @@ impl Common {
|
||||
{
|
||||
EventListenerHandle::new(self.raw.clone(), event_name, Closure::new(handler))
|
||||
}
|
||||
}
|
||||
|
||||
// The difference between add_event and add_user_event is that the latter has a special meaning
|
||||
// for browser security. A user event is a deliberate action by the user (like a mouse or key
|
||||
// press) and is the only time things like a fullscreen request may be successfully completed.)
|
||||
pub fn add_transient_event<E, F>(
|
||||
&self,
|
||||
event_name: &'static str,
|
||||
mut handler: F,
|
||||
) -> EventListenerHandle<dyn FnMut(E)>
|
||||
where
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
F: 'static + FnMut(E),
|
||||
{
|
||||
let fullscreen_handler = Rc::downgrade(&self.fullscreen_handler);
|
||||
impl Style {
|
||||
fn new(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> Self {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let read = window
|
||||
.get_computed_style(canvas)
|
||||
.expect("Failed to obtain computed style")
|
||||
// this can't fail: we aren't using a pseudo-element
|
||||
.expect("Invalid pseudo-element");
|
||||
|
||||
self.add_event(event_name, move |event: E| {
|
||||
handler(event);
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let write = canvas.style();
|
||||
|
||||
if let Some(fullscreen_handler) = Weak::upgrade(&fullscreen_handler) {
|
||||
fullscreen_handler.transient_activation()
|
||||
}
|
||||
})
|
||||
Self { read, write }
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, property: &str) -> String {
|
||||
self.read
|
||||
.get_property_value(property)
|
||||
.expect("Invalid property")
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&self, property: &str) {
|
||||
self.write
|
||||
.remove_property(property)
|
||||
.expect("Property is read only");
|
||||
}
|
||||
|
||||
pub(crate) fn set(&self, property: &str, value: &str) {
|
||||
self.write
|
||||
.set_property(property, value)
|
||||
.expect("Property is read only");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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};
|
||||
@@ -80,22 +81,9 @@ 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(),
|
||||
y: event.offset_y(),
|
||||
x: event.offset_x() as f64,
|
||||
y: event.offset_y() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +244,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> = const { OnceCell::new() };
|
||||
static POINTER_RAW_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
POINTER_RAW_SUPPORT.with(|support| {
|
||||
@@ -279,7 +267,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> = const { OnceCell::new() };
|
||||
static COALESCED_EVENTS_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
COALESCED_EVENTS_SUPPORT.with(|support| {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use js_sys::Promise;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
@@ -8,138 +5,86 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{Document, Element, HtmlCanvasElement};
|
||||
|
||||
use super::EventListenerHandle;
|
||||
pub fn request_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
|
||||
if is_fullscreen(document, canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(extends = HtmlCanvasElement)]
|
||||
type RequestFullscreen;
|
||||
|
||||
#[wasm_bindgen(method, js_name = requestFullscreen)]
|
||||
fn request_fullscreen(this: &RequestFullscreen) -> Promise;
|
||||
|
||||
#[wasm_bindgen(method, js_name = webkitRequestFullscreen)]
|
||||
fn webkit_request_fullscreen(this: &RequestFullscreen);
|
||||
}
|
||||
|
||||
let canvas: &RequestFullscreen = canvas.unchecked_ref();
|
||||
|
||||
if has_fullscreen_api_support(canvas) {
|
||||
thread_local! {
|
||||
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
|
||||
}
|
||||
REJECT_HANDLER.with(|handler| {
|
||||
let _ = canvas.request_fullscreen().catch(handler);
|
||||
});
|
||||
} else {
|
||||
canvas.webkit_request_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FullscreenHandler {
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
fullscreen_requested: Rc<Cell<bool>>,
|
||||
_fullscreen_change: EventListenerHandle<dyn FnMut()>,
|
||||
pub fn is_fullscreen(document: &Document, canvas: &HtmlCanvasElement) -> bool {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type FullscreenElement;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)]
|
||||
fn webkit_fullscreen_element(this: &FullscreenElement) -> Option<Element>;
|
||||
}
|
||||
|
||||
let element = if has_fullscreen_api_support(canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
document.fullscreen_element()
|
||||
} else {
|
||||
let document: &FullscreenElement = document.unchecked_ref();
|
||||
document.webkit_fullscreen_element()
|
||||
};
|
||||
|
||||
match element {
|
||||
Some(element) => {
|
||||
let canvas: &Element = canvas;
|
||||
canvas == &element
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl FullscreenHandler {
|
||||
pub fn new(document: Document, canvas: HtmlCanvasElement) -> Self {
|
||||
let fullscreen_requested = Rc::new(Cell::new(false));
|
||||
let fullscreen_change = EventListenerHandle::new(
|
||||
canvas.clone(),
|
||||
if has_fullscreen_api_support(&canvas) {
|
||||
"fullscreenchange"
|
||||
} else {
|
||||
"webkitfullscreenchange"
|
||||
},
|
||||
Closure::new({
|
||||
let fullscreen_requested = fullscreen_requested.clone();
|
||||
move || {
|
||||
// It doesn't matter if the canvas entered or exitted fullscreen mode,
|
||||
// we don't want to request it again later.
|
||||
fullscreen_requested.set(false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
pub fn exit_fullscreen(document: &Document, canvas: &HtmlCanvasElement) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type ExitFullscreen;
|
||||
|
||||
Self {
|
||||
document,
|
||||
canvas,
|
||||
fullscreen_requested,
|
||||
_fullscreen_change: fullscreen_change,
|
||||
}
|
||||
#[wasm_bindgen(method, js_name = webkitExitFullscreen)]
|
||||
fn webkit_exit_fullscreen(this: &ExitFullscreen);
|
||||
}
|
||||
|
||||
fn internal_request_fullscreen(&self) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type RequestFullscreen;
|
||||
|
||||
#[wasm_bindgen(method, js_name = requestFullscreen)]
|
||||
fn request_fullscreen(this: &RequestFullscreen) -> Promise;
|
||||
|
||||
#[wasm_bindgen(method, js_name = webkitRequestFullscreen)]
|
||||
fn webkit_request_fullscreen(this: &RequestFullscreen);
|
||||
}
|
||||
|
||||
let canvas: &RequestFullscreen = self.canvas.unchecked_ref();
|
||||
|
||||
if has_fullscreen_api_support(&self.canvas) {
|
||||
thread_local! {
|
||||
static REJECT_HANDLER: Closure<dyn FnMut(JsValue)> = Closure::new(|_| ());
|
||||
}
|
||||
REJECT_HANDLER.with(|handler| {
|
||||
let _ = canvas.request_fullscreen().catch(handler);
|
||||
});
|
||||
} else {
|
||||
canvas.webkit_request_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
if !self.is_fullscreen() {
|
||||
self.internal_request_fullscreen();
|
||||
self.fullscreen_requested.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transient_activation(&self) {
|
||||
if self.fullscreen_requested.get() {
|
||||
self.internal_request_fullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_fullscreen(&self) -> bool {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type FullscreenElement;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)]
|
||||
fn webkit_fullscreen_element(this: &FullscreenElement) -> Option<Element>;
|
||||
}
|
||||
|
||||
let element = if has_fullscreen_api_support(&self.canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
self.document.fullscreen_element()
|
||||
} else {
|
||||
let document: &FullscreenElement = self.document.unchecked_ref();
|
||||
document.webkit_fullscreen_element()
|
||||
};
|
||||
|
||||
match element {
|
||||
Some(element) => {
|
||||
let canvas: &Element = &self.canvas;
|
||||
canvas == &element
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_fullscreen(&self) {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type ExitFullscreen;
|
||||
|
||||
#[wasm_bindgen(method, js_name = webkitExitFullscreen)]
|
||||
fn webkit_exit_fullscreen(this: &ExitFullscreen);
|
||||
}
|
||||
|
||||
if has_fullscreen_api_support(&self.canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
self.document.exit_fullscreen()
|
||||
} else {
|
||||
let document: &ExitFullscreen = self.document.unchecked_ref();
|
||||
document.webkit_exit_fullscreen()
|
||||
}
|
||||
|
||||
self.fullscreen_requested.set(false);
|
||||
}
|
||||
|
||||
pub fn cancel(&self) {
|
||||
self.fullscreen_requested.set(false);
|
||||
if has_fullscreen_api_support(canvas) {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
document.exit_fullscreen()
|
||||
} else {
|
||||
let document: &ExitFullscreen = document.unchecked_ref();
|
||||
document.webkit_exit_fullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
fn has_fullscreen_api_support(canvas: &HtmlCanvasElement) -> bool {
|
||||
thread_local! {
|
||||
static FULLSCREEN_API_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
FULLSCREEN_API_SUPPORT.with(|support| {
|
||||
*support.get_or_init(|| {
|
||||
#[wasm_bindgen]
|
||||
|
||||
@@ -10,6 +10,7 @@ mod resize_scaling;
|
||||
mod schedule;
|
||||
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::canvas::Style;
|
||||
pub use self::event::ButtonsState;
|
||||
pub use self::event_handle::EventListenerHandle;
|
||||
pub use self::resize_scaling::ResizeScaleHandle;
|
||||
@@ -17,9 +18,7 @@ pub use self::schedule::Schedule;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState,
|
||||
};
|
||||
use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState};
|
||||
|
||||
pub fn throw(msg: &str) {
|
||||
wasm_bindgen::throw_str(msg);
|
||||
@@ -51,8 +50,8 @@ pub fn scale_factor(window: &web_sys::Window) -> f64 {
|
||||
window.device_pixel_ratio()
|
||||
}
|
||||
|
||||
fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> LogicalSize<f64> {
|
||||
if style.get_property_value("box-sizing").unwrap() == "border-box" {
|
||||
fn fix_canvas_size(style: &Style, mut size: LogicalSize<f64>) -> LogicalSize<f64> {
|
||||
if style.get("box-sizing") == "border-box" {
|
||||
size.width += style_size_property(style, "border-left-width")
|
||||
+ style_size_property(style, "border-right-width")
|
||||
+ style_size_property(style, "padding-left")
|
||||
@@ -69,76 +68,68 @@ fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> L
|
||||
pub fn set_canvas_size(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
new_size: LogicalSize<f64>,
|
||||
) {
|
||||
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
|
||||
if !document.contains(Some(raw)) || style.get("display") == "none" {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_size = fix_canvas_size(style, new_size);
|
||||
|
||||
set_canvas_style_property(raw, "width", &format!("{}px", new_size.width));
|
||||
set_canvas_style_property(raw, "height", &format!("{}px", new_size.height));
|
||||
style.set("width", &format!("{}px", new_size.width));
|
||||
style.set("height", &format!("{}px", new_size.height));
|
||||
}
|
||||
|
||||
pub fn set_canvas_min_size(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
dimensions: Option<LogicalSize<f64>>,
|
||||
) {
|
||||
if let Some(dimensions) = dimensions {
|
||||
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
|
||||
if !document.contains(Some(raw)) || style.get("display") == "none" {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_size = fix_canvas_size(style, dimensions);
|
||||
|
||||
set_canvas_style_property(raw, "min-width", &format!("{}px", new_size.width));
|
||||
set_canvas_style_property(raw, "min-height", &format!("{}px", new_size.height));
|
||||
style.set("min-width", &format!("{}px", new_size.width));
|
||||
style.set("min-height", &format!("{}px", new_size.height));
|
||||
} else {
|
||||
style
|
||||
.remove_property("min-width")
|
||||
.expect("Property is read only");
|
||||
style
|
||||
.remove_property("min-height")
|
||||
.expect("Property is read only");
|
||||
style.remove("min-width");
|
||||
style.remove("min-height");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_canvas_max_size(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
dimensions: Option<LogicalSize<f64>>,
|
||||
) {
|
||||
if let Some(dimensions) = dimensions {
|
||||
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" {
|
||||
if !document.contains(Some(raw)) || style.get("display") == "none" {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_size = fix_canvas_size(style, dimensions);
|
||||
|
||||
set_canvas_style_property(raw, "max-width", &format!("{}px", new_size.width));
|
||||
set_canvas_style_property(raw, "max-height", &format!("{}px", new_size.height));
|
||||
style.set("max-width", &format!("{}px", new_size.width));
|
||||
style.set("max-height", &format!("{}px", new_size.height));
|
||||
} else {
|
||||
style
|
||||
.remove_property("max-width")
|
||||
.expect("Property is read only");
|
||||
style
|
||||
.remove_property("max-height")
|
||||
.expect("Property is read only");
|
||||
style.remove("max-width");
|
||||
style.remove("max-height");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_canvas_position(
|
||||
document: &Document,
|
||||
raw: &HtmlCanvasElement,
|
||||
style: &CssStyleDeclaration,
|
||||
style: &Style,
|
||||
mut position: LogicalPosition<f64>,
|
||||
) {
|
||||
if document.contains(Some(raw)) && style.get_property_value("display").unwrap() != "none" {
|
||||
if document.contains(Some(raw)) && style.get("display") != "none" {
|
||||
position.x -= style_size_property(style, "margin-left")
|
||||
+ style_size_property(style, "border-left-width")
|
||||
+ style_size_property(style, "padding-left");
|
||||
@@ -147,30 +138,21 @@ pub fn set_canvas_position(
|
||||
+ style_size_property(style, "padding-top");
|
||||
}
|
||||
|
||||
set_canvas_style_property(raw, "position", "fixed");
|
||||
set_canvas_style_property(raw, "left", &format!("{}px", position.x));
|
||||
set_canvas_style_property(raw, "top", &format!("{}px", position.y));
|
||||
style.set("position", "fixed");
|
||||
style.set("left", &format!("{}px", position.x));
|
||||
style.set("top", &format!("{}px", position.y));
|
||||
}
|
||||
|
||||
/// This function will panic if the element is not inserted in the DOM
|
||||
/// or is not a CSS property that represents a size in pixel.
|
||||
pub fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 {
|
||||
let prop = style
|
||||
.get_property_value(property)
|
||||
.expect("Found invalid property");
|
||||
pub fn style_size_property(style: &Style, property: &str) -> f64 {
|
||||
let prop = style.get(property);
|
||||
prop.strip_suffix("px")
|
||||
.expect("Element was not inserted into the DOM or is not a size in pixel")
|
||||
.parse()
|
||||
.expect("CSS property is not a size in pixel")
|
||||
}
|
||||
|
||||
pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) {
|
||||
let style = raw.style();
|
||||
style
|
||||
.set_property(property, value)
|
||||
.unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}"))
|
||||
}
|
||||
|
||||
pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> {
|
||||
window
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
|
||||
@@ -80,7 +80,7 @@ impl PointerHandler {
|
||||
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
|
||||
{
|
||||
let window = canvas_common.window.clone();
|
||||
self.on_pointer_release = Some(canvas_common.add_transient_event(
|
||||
self.on_pointer_release = Some(canvas_common.add_event(
|
||||
"pointerup",
|
||||
move |event: PointerEvent| {
|
||||
let modifiers = event::mouse_modifiers(&event);
|
||||
@@ -118,7 +118,7 @@ impl PointerHandler {
|
||||
{
|
||||
let window = canvas_common.window.clone();
|
||||
let canvas = canvas_common.raw.clone();
|
||||
self.on_pointer_press = Some(canvas_common.add_transient_event(
|
||||
self.on_pointer_press = Some(canvas_common.add_event(
|
||||
"pointerdown",
|
||||
move |event: PointerEvent| {
|
||||
if prevent_default {
|
||||
|
||||
@@ -2,14 +2,14 @@ use js_sys::{Array, Object};
|
||||
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, HtmlCanvasElement, MediaQueryList, ResizeObserver,
|
||||
ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize,
|
||||
Window,
|
||||
Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions,
|
||||
ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window,
|
||||
};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
|
||||
use super::super::backend;
|
||||
use super::canvas::Style;
|
||||
use super::media_query_handle::MediaQueryListHandle;
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
@@ -22,7 +22,7 @@ impl ResizeScaleHandle {
|
||||
window: Window,
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
style: Style,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Self
|
||||
@@ -51,7 +51,7 @@ struct ResizeScaleInternal {
|
||||
window: Window,
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
style: Style,
|
||||
mql: MediaQueryListHandle,
|
||||
observer: ResizeObserver,
|
||||
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
|
||||
@@ -65,7 +65,7 @@ impl ResizeScaleInternal {
|
||||
window: Window,
|
||||
document: Document,
|
||||
canvas: HtmlCanvasElement,
|
||||
style: CssStyleDeclaration,
|
||||
style: Style,
|
||||
scale_handler: S,
|
||||
resize_handler: R,
|
||||
) -> Rc<RefCell<Self>>
|
||||
@@ -126,7 +126,7 @@ impl ResizeScaleInternal {
|
||||
(-webkit-device-pixel-ratio: {current_scale})",
|
||||
);
|
||||
let mql = MediaQueryListHandle::new(window, &media_query, closure);
|
||||
debug_assert!(
|
||||
assert!(
|
||||
mql.mql().matches(),
|
||||
"created media query doesn't match, {current_scale} != {}",
|
||||
super::scale_factor(window)
|
||||
@@ -152,9 +152,7 @@ impl ResizeScaleInternal {
|
||||
}
|
||||
|
||||
fn notify(&mut self) {
|
||||
if !self.document.contains(Some(&self.canvas))
|
||||
|| self.style.get_property_value("display").unwrap() == "none"
|
||||
{
|
||||
if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
|
||||
let size = PhysicalSize::new(0, 0);
|
||||
|
||||
if self.notify_scale.replace(false) {
|
||||
@@ -180,7 +178,7 @@ impl ResizeScaleInternal {
|
||||
backend::style_size_property(&self.style, "height"),
|
||||
);
|
||||
|
||||
if self.style.get_property_value("box-sizing").unwrap() == "border-box" {
|
||||
if self.style.get("box-sizing") == "border-box" {
|
||||
size.width -= backend::style_size_property(&self.style, "border-left-width")
|
||||
+ backend::style_size_property(&self.style, "border-right-width")
|
||||
+ backend::style_size_property(&self.style, "padding-left")
|
||||
@@ -246,10 +244,7 @@ impl ResizeScaleInternal {
|
||||
.get(0)
|
||||
.unchecked_into();
|
||||
|
||||
let writing_mode = self
|
||||
.style
|
||||
.get_property_value("writing-mode")
|
||||
.expect("`writing-mode` is a valid CSS property");
|
||||
let writing_mode = self.style.get("writing-mode");
|
||||
|
||||
// means the canvas is not inserted into the DOM
|
||||
if writing_mode.is_empty() {
|
||||
|
||||
@@ -117,7 +117,9 @@ impl Schedule {
|
||||
let channel = MessageChannel::new().unwrap();
|
||||
let closure = Closure::new(f);
|
||||
let port_1 = channel.port1();
|
||||
port_1.set_onmessage(Some(closure.as_ref().unchecked_ref()));
|
||||
port_1
|
||||
.add_event_listener_with_callback("message", closure.as_ref().unchecked_ref())
|
||||
.expect("Failed to set message handler");
|
||||
port_1.start();
|
||||
|
||||
let port_2 = channel.port2();
|
||||
@@ -176,7 +178,6 @@ impl Drop for Schedule {
|
||||
} => {
|
||||
window.clear_timeout_with_handle(*handle);
|
||||
port.close();
|
||||
port.set_onmessage(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +199,7 @@ fn duration_millis_ceil(duration: Duration) -> u32 {
|
||||
|
||||
fn has_scheduler_support(window: &web_sys::Window) -> bool {
|
||||
thread_local! {
|
||||
static SCHEDULER_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
||||
static SCHEDULER_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
SCHEDULER_SUPPORT.with(|support| {
|
||||
@@ -220,7 +221,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> = const { OnceCell::new() };
|
||||
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
IDLE_CALLBACK_SUPPORT.with(|support| {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
|
||||
use crate::icon::Icon;
|
||||
@@ -7,12 +8,12 @@ use crate::window::{
|
||||
};
|
||||
use crate::SendSyncWrapper;
|
||||
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use super::cursor::SelectedCursor;
|
||||
use super::r#async::Dispatcher;
|
||||
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -24,7 +25,8 @@ pub struct Inner {
|
||||
id: WindowId,
|
||||
pub window: web_sys::Window,
|
||||
canvas: Rc<RefCell<backend::Canvas>>,
|
||||
previous_pointer: RefCell<&'static str>,
|
||||
selected_cursor: RefCell<SelectedCursor>,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
destroy_fn: Option<Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
@@ -53,7 +55,8 @@ impl Window {
|
||||
id,
|
||||
window: window.clone(),
|
||||
canvas,
|
||||
previous_pointer: RefCell::new("auto"),
|
||||
selected_cursor: Default::default(),
|
||||
cursor_visible: Rc::new(Cell::new(true)),
|
||||
destroy_fn: Some(destroy_fn),
|
||||
};
|
||||
|
||||
@@ -195,8 +198,24 @@ impl Inner {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
*self.previous_pointer.borrow_mut() = cursor.name();
|
||||
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name());
|
||||
*self.selected_cursor.borrow_mut() = SelectedCursor::Named(cursor);
|
||||
|
||||
if self.cursor_visible.get() {
|
||||
self.canvas.borrow().style().set("cursor", cursor.name());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
|
||||
let canvas = self.canvas.borrow();
|
||||
let new_cursor = cursor.inner.build(
|
||||
canvas.window(),
|
||||
canvas.document(),
|
||||
canvas.style(),
|
||||
self.selected_cursor.take(),
|
||||
self.cursor_visible.clone(),
|
||||
);
|
||||
*self.selected_cursor.borrow_mut() = new_cursor;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -222,14 +241,14 @@ impl Inner {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
if !visible {
|
||||
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none");
|
||||
} else {
|
||||
backend::set_canvas_style_property(
|
||||
self.canvas.borrow().raw(),
|
||||
"cursor",
|
||||
&self.previous_pointer.borrow(),
|
||||
);
|
||||
if !visible && self.cursor_visible.get() {
|
||||
self.canvas.borrow().style().set("cursor", "none");
|
||||
self.cursor_visible.set(false);
|
||||
} else if visible && !self.cursor_visible.get() {
|
||||
self.selected_cursor
|
||||
.borrow()
|
||||
.set_style(self.canvas.borrow().style());
|
||||
self.cursor_visible.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use windows_sys::Win32::{
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
||||
MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, MDT_EFFECTIVE_DPI,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
},
|
||||
WindowsAndMessaging::IsProcessDPIAware,
|
||||
},
|
||||
@@ -21,6 +21,8 @@ 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(|| {
|
||||
|
||||
0
src/platform_impl/windows/event.rs
Normal file
0
src/platform_impl/windows/event.rs
Normal file
@@ -21,7 +21,7 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE,
|
||||
Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Graphics::Gdi::{
|
||||
GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient,
|
||||
ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE,
|
||||
@@ -35,9 +35,13 @@ use windows_sys::Win32::{
|
||||
Input::{
|
||||
Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW},
|
||||
KeyboardAndMouse::{
|
||||
ReleaseCapture, SetCapture, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT,
|
||||
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,
|
||||
},
|
||||
Pointer::{POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE},
|
||||
Touch::{
|
||||
CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE,
|
||||
TOUCHEVENTF_UP, TOUCHINPUT,
|
||||
@@ -50,19 +54,20 @@ 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_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_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,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -75,8 +80,8 @@ use crate::{
|
||||
WindowEvent,
|
||||
},
|
||||
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
keyboard::ModifiersState,
|
||||
platform::pump_events::PumpStatus,
|
||||
keyboard::{KeyCode, ModifiersState, PhysicalKey},
|
||||
platform::{pump_events::PumpStatus, scancode::PhysicalKeyExtScancode},
|
||||
platform_impl::platform::{
|
||||
dark_mode::try_theme,
|
||||
dpi::{become_dpi_aware, dpi_to_scale_factor},
|
||||
@@ -96,7 +101,38 @@ use runner::{EventLoopRunner, EventLoopRunnerShared};
|
||||
|
||||
use self::runner::RunnerState;
|
||||
|
||||
use super::window::set_skip_taskbar;
|
||||
use super::{window::set_skip_taskbar, SelectedCursor};
|
||||
|
||||
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>>,
|
||||
@@ -530,10 +566,6 @@ 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()
|
||||
}
|
||||
@@ -696,13 +728,14 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
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))
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1798,9 +1831,9 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
Some(SkipPointerFrameMessages),
|
||||
Some(GetPointerDeviceRects),
|
||||
) = (
|
||||
*util::GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*util::SKIP_POINTER_FRAME_MESSAGES,
|
||||
*util::GET_POINTER_DEVICE_RECTS,
|
||||
*GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*SKIP_POINTER_FRAME_MESSAGES,
|
||||
*GET_POINTER_DEVICE_RECTS,
|
||||
) {
|
||||
let pointer_id = super::loword(wparam as u32) as u32;
|
||||
let mut entries_count = 0u32;
|
||||
@@ -1882,7 +1915,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
let force = match pointer_info.pointerType {
|
||||
PT_TOUCH => {
|
||||
let mut touch_info = mem::MaybeUninit::uninit();
|
||||
util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
match unsafe {
|
||||
GetPointerTouchInfo(
|
||||
pointer_info.pointerId,
|
||||
@@ -1898,7 +1931,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
}
|
||||
PT_PEN => {
|
||||
let mut pen_info = mem::MaybeUninit::uninit();
|
||||
util::GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
match unsafe {
|
||||
GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr())
|
||||
} {
|
||||
@@ -1978,16 +2011,21 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
// `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement.
|
||||
let in_client_area = super::loword(lparam as u32) as u32 == HTCLIENT;
|
||||
if in_client_area {
|
||||
Some(window_state.mouse.cursor)
|
||||
Some(window_state.mouse.selected_cursor.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
match set_cursor_to {
|
||||
Some(cursor) => {
|
||||
let cursor = unsafe { LoadCursorW(0, util::to_windows_cursor(cursor)) };
|
||||
unsafe { SetCursor(cursor) };
|
||||
Some(selected_cursor) => {
|
||||
let hcursor = match selected_cursor {
|
||||
SelectedCursor::Named(cursor_icon) => unsafe {
|
||||
LoadCursorW(0, util::to_windows_cursor(cursor_icon))
|
||||
},
|
||||
SelectedCursor::Custom(cursor) => cursor.as_raw_handle(),
|
||||
};
|
||||
unsafe { SetCursor(hcursor) };
|
||||
result = ProcResult::Value(0);
|
||||
}
|
||||
None => result = ProcResult::DefWindowProc(wparam),
|
||||
@@ -2460,17 +2498,105 @@ unsafe fn handle_raw_input<T: 'static>(userdata: &ThreadMsgTargetData<T>, data:
|
||||
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 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;
|
||||
}
|
||||
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,10 +163,6 @@ 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();
|
||||
@@ -403,29 +399,23 @@ impl<T> BufferedEvent<T> {
|
||||
match self {
|
||||
Self::Event(event) => dispatch(event),
|
||||
Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => {
|
||||
let user_new_innner_size = Arc::new(Mutex::new(new_inner_size));
|
||||
let new_inner_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(
|
||||
&user_new_innner_size,
|
||||
)),
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
||||
},
|
||||
});
|
||||
let inner_size = *user_new_innner_size.lock().unwrap();
|
||||
let inner_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user