mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
15 Commits
v0.29.0-be
...
v0.28.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d102c21792 | ||
|
|
68ed564def | ||
|
|
339d57b646 | ||
|
|
2e4dafc9fe | ||
|
|
0fbba02318 | ||
|
|
41e524f12c | ||
|
|
644c47a6f8 | ||
|
|
0be08e574d | ||
|
|
811cc5cdb7 | ||
|
|
230b37df56 | ||
|
|
09bca59cf3 | ||
|
|
9fb8aaa6f4 | ||
|
|
546ab7575e | ||
|
|
3e258a377f | ||
|
|
e5260da95b |
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -28,8 +28,8 @@
|
||||
/src/platform_impl/macos @madsmtm
|
||||
|
||||
# Web (no maintainer)
|
||||
/src/platform/web.rs @daxpedda
|
||||
/src/platform_impl/web @daxpedda
|
||||
/src/platform/web.rs
|
||||
/src/platform_impl/web
|
||||
|
||||
# Windows
|
||||
/src/platform/windows.rs @msiglreith
|
||||
|
||||
44
.github/workflows/ci.yml
vendored
44
.github/workflows/ci.yml
vendored
@@ -9,50 +9,20 @@ jobs:
|
||||
Check_Formatting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: stable
|
||||
components: rustfmt
|
||||
- name: Check Formatting
|
||||
run: cargo +stable fmt --all -- --check
|
||||
|
||||
cargo-deny:
|
||||
name: cargo-deny
|
||||
|
||||
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- aarch64-apple-ios
|
||||
- aarch64-linux-android
|
||||
- i686-pc-windows-gnu
|
||||
- i686-pc-windows-msvc
|
||||
- i686-unknown-linux-gnu
|
||||
- wasm32-unknown-unknown
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-apple-ios
|
||||
- x86_64-pc-windows-gnu
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-redox
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
with:
|
||||
command: check
|
||||
log-level: error
|
||||
arguments: --all-features --target ${{ matrix.platform }}
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust_version: ['1.64.0', stable, nightly]
|
||||
rust_version: ['1.60.0', stable, nightly]
|
||||
platform:
|
||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||
@@ -83,7 +53,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
# Used to cache cargo-web
|
||||
- name: Cache cargo folder
|
||||
uses: actions/cache@v1
|
||||
@@ -116,7 +86,7 @@ jobs:
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.60.0'
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Run tests
|
||||
@@ -126,7 +96,7 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.60.0'
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
||||
|
||||
- name: Lint with clippy
|
||||
@@ -138,7 +108,7 @@ jobs:
|
||||
shell: bash
|
||||
if: >
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.60.0'
|
||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
- name: Run tests with serde enabled
|
||||
shell: bash
|
||||
@@ -147,5 +117,5 @@ jobs:
|
||||
!contains(matrix.platform.target, 'ios') &&
|
||||
!contains(matrix.platform.target, 'wasm32') &&
|
||||
!contains(matrix.platform.target, 'redox') &&
|
||||
matrix.rust_version != '1.64.0'
|
||||
matrix.rust_version != '1.60.0'
|
||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
||||
|
||||
104
CHANGELOG.md
104
CHANGELOG.md
@@ -8,108 +8,6 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.29.0-beta.0
|
||||
|
||||
- On Web, allow event loops to be recreated with `spawn`.
|
||||
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
|
||||
- On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them.
|
||||
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
|
||||
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
|
||||
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
|
||||
- On X11, fix `EventLoopWindowTarget::listen_device_events` effect being reversed.
|
||||
- **Breaking:** Remove all deprecated `modifiers` fields.
|
||||
- **Breaking:** Overhaul keyboard input handling.
|
||||
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
|
||||
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
|
||||
- Change `Event::Key` to contain a `RawKeyEvent`.
|
||||
- Remove `Event::ReceivedCharacter`. In its place, you should use
|
||||
`KeyEvent.text` in combination with `WindowEvent::Ime`.
|
||||
- Replace `VirtualKeyCode` with the `Key` enum.
|
||||
- Replace `ScanCode` with the `KeyCode` enum.
|
||||
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
|
||||
- Add `KeyCode` to refer to keys (roughly) by their physical location.
|
||||
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
|
||||
understand.
|
||||
- Add `Key` to represent the keys after they've been interpreted by the
|
||||
active (software) keyboard layout.
|
||||
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
|
||||
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
|
||||
but can appear simultaneously in different spots on the same keyboard
|
||||
layout.
|
||||
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
|
||||
of dead key sequences.
|
||||
- Add `KeyEventExtModifierSupplement` to expose additional (and less
|
||||
portable) interpretations of a given key-press.
|
||||
- Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and
|
||||
`KeyCode`.
|
||||
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
|
||||
- On Orbital, fix `ModifiersChanged` not being sent.
|
||||
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
|
||||
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
|
||||
- **Breaking:** `CursorIcon::Arrow` was removed.
|
||||
- On Wayland, fix maximized startup not taking full size on GNOME.
|
||||
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
|
||||
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
|
||||
- On Wayland, fix window not checking that it actually got initial configure event.
|
||||
- On Wayland, fix maximized window creation and window geometry handling.
|
||||
- On Wayland, fix forward compatibility issues.
|
||||
- On Wayland, add `Window::drag_resize_window` method.
|
||||
- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable.
|
||||
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
|
||||
- **Breaking:** Bump MSRV from `1.60` to `1.64`.
|
||||
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
|
||||
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
|
||||
- On Web: fix position of touch events to be relative to the canvas.
|
||||
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during
|
||||
a transient activation.
|
||||
- On Web, fix pointer button events not being processed when a buttons is already pressed.
|
||||
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
|
||||
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
|
||||
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
|
||||
- On Windows, port to `windows-sys` version 0.48.0.
|
||||
- On Web, fix pen treated as mouse input.
|
||||
- On Web, send mouse position on button release as well.
|
||||
- On Web, fix touch input not gaining or loosing focus.
|
||||
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
|
||||
- On Web, prevent clicks on the canvas to select text.
|
||||
- On Web, `EventLoopProxy` now implements `Send`.
|
||||
- On Web, `Window` now implements `Send` and `Sync`.
|
||||
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
|
||||
- On Web, use the correct canvas size when calculating the new size during scale factor change,
|
||||
instead of using the output bitmap size.
|
||||
- On Web, scale factor and dark mode detection are now more robust.
|
||||
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
|
||||
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
|
||||
- On macOS, fix crash when dropping `Window`.
|
||||
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available.
|
||||
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to
|
||||
the canvas size will be reported through `WindowEvent::Resized`.
|
||||
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
|
||||
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
|
||||
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and
|
||||
`DeviceEvent::Key` support.
|
||||
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events
|
||||
on Wayland, X11, Windows, macOS and Web.
|
||||
|
||||
# 0.28.6
|
||||
|
||||
- On macOS, fixed memory leak when getting monitor handle.
|
||||
- On macOS, fix `Backspace` being emitted when clearing preedit with it.
|
||||
|
||||
# 0.28.5
|
||||
|
||||
- On macOS, fix `key_up` being ignored when `Ime` is disabled.
|
||||
|
||||
# 0.28.4
|
||||
|
||||
- On macOS, fix empty marked text blocking regular input.
|
||||
- On macOS, fix potential panic when getting refresh rate.
|
||||
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.
|
||||
|
||||
# 0.28.3
|
||||
|
||||
- Fix macOS memory leaks.
|
||||
|
||||
# 0.28.2
|
||||
|
||||
- Implement `HasRawDisplayHandle` for `EventLoop`.
|
||||
@@ -396,7 +294,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||
|
||||
# 0.23.0 (2020-10-02)
|
||||
|
||||
- On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode.
|
||||
- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode.
|
||||
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
|
||||
- On Unix, X11 and Wayland are now optional features (enabled by default)
|
||||
- On X11, fix deadlock when calling `set_fullscreen_inner`.
|
||||
|
||||
@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
|
||||
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.64.
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.60.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||
- you updated any relevant documentation in winit
|
||||
|
||||
79
Cargo.toml
79
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.0-beta.0"
|
||||
version = "0.28.2"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -10,7 +10,7 @@ readme = "README.md"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
rust-version = "1.64.0"
|
||||
rust-version = "1.60.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde"]
|
||||
@@ -36,55 +36,46 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
x11 = ["x11-dl", "mio", "percent-encoding"]
|
||||
wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"]
|
||||
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
|
||||
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
||||
android-native-activity = ["android-activity/native-activity"]
|
||||
android-game-activity = ["android-activity/game-activity"]
|
||||
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
|
||||
android-native-activity = [ "android-activity/native-activity" ]
|
||||
android-game-activity = [ "android-activity/game-activity" ]
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
cursor-icon = "1.0.0"
|
||||
bitflags = "1"
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
log = "0.4"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
once_cell = "1.12"
|
||||
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
smol_str = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
||||
simple_logger = { version = "2.1.0", default_features = false }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
|
||||
softbuffer = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
|
||||
android-activity = "0.4.0"
|
||||
ndk = "0.7.0"
|
||||
ndk-sys = "0.4.0"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks`
|
||||
objc2 = "=0.3.0-beta.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.22.3"
|
||||
dispatch = "0.2.0"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
unicode-segmentation = "1.7.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
version = "0.45"
|
||||
features = [
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Foundation",
|
||||
@@ -112,62 +103,56 @@ features = [
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_arch = "wasm32", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||
libc = "0.2.64"
|
||||
mio = { version = "0.8", features = ["os-ext"], optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
fnv = { version = "1.0.3", optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.6.0", default_features = false, optional = true }
|
||||
wayland-client = { version = "0.30.0", optional = true }
|
||||
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
|
||||
calloop = "0.10.5"
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.5.1", default_features = false, optional = true }
|
||||
wayland-client = { version = "0.29.5", default_features = false, features = ["use_system_lib"], optional = true }
|
||||
wayland-protocols = { version = "0.29.5", features = [ "staging_protocols"], optional = true }
|
||||
wayland-commons = { version = "0.29.5", optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
memmap2 = { version = "0.5.0", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
orbclient = { version = "0.3.42", default-features = false }
|
||||
redox_syscall = "0.3"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.build-dependencies]
|
||||
wayland-scanner = "0.29.5"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
||||
package = "web-sys"
|
||||
version = "0.3.64"
|
||||
version = "0.3.22"
|
||||
features = [
|
||||
'console',
|
||||
"AddEventListenerOptions",
|
||||
'CssStyleDeclaration',
|
||||
'BeforeUnloadEvent',
|
||||
'Document',
|
||||
'DomRect',
|
||||
'DomRectReadOnly',
|
||||
'Element',
|
||||
'Event',
|
||||
"EventListenerOptions",
|
||||
'EventTarget',
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MediaQueryListEvent',
|
||||
'MouseEvent',
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
'ResizeObserver',
|
||||
'ResizeObserverBoxOptions',
|
||||
'ResizeObserverEntry',
|
||||
'ResizeObserverOptions',
|
||||
'ResizeObserverSize',
|
||||
'Window',
|
||||
'WheelEvent'
|
||||
]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
atomic-waker = "1"
|
||||
js-sys = "0.3.64"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-time = "0.2"
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
||||
version = "0.2.45"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
||||
console_log = "1"
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
console_log = "0.2"
|
||||
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
||||
|
||||
[workspace]
|
||||
|
||||
@@ -222,9 +222,9 @@ Changes in the API that have been agreed upon but aren't implemented across all
|
||||
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|
||||
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
||||
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |❓ |
|
||||
|
||||
### Completed API Reworks
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|
||||
@@ -243,5 +243,5 @@ Changes in the API that have been agreed upon but aren't implemented across all
|
||||
[#720]: https://github.com/rust-windowing/winit/issues/720
|
||||
[#721]: https://github.com/rust-windowing/winit/issues/721
|
||||
[#750]: https://github.com/rust-windowing/winit/issues/750
|
||||
[#753]: https://github.com/rust-windowing/winit/issues/753
|
||||
[#804]: https://github.com/rust-windowing/winit/issues/804
|
||||
[#812]: https://github.com/rust-windowing/winit/issues/812
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.0-beta.0"
|
||||
winit = "0.28.2"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -73,6 +73,10 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
|
||||
Note that windows don't appear on Wayland until you draw/present to them.
|
||||
|
||||
`winit` doesn't do drawing, try the examples in [`glutin`] instead.
|
||||
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
|
||||
#### WebAssembly
|
||||
|
||||
To run the web example: `cargo run-wasm --example web`
|
||||
|
||||
42
build.rs
42
build.rs
@@ -1,14 +1,41 @@
|
||||
use cfg_aliases::cfg_aliases;
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
),
|
||||
feature = "wayland",
|
||||
))]
|
||||
mod wayland {
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use wayland_scanner::Side;
|
||||
|
||||
pub fn main() {
|
||||
let mut path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
path.push("fractional_scale_v1.rs");
|
||||
wayland_scanner::generate_code(
|
||||
"wayland_protocols/fractional-scale-v1.xml",
|
||||
&path,
|
||||
Side::Client,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// The script doesn't depend on our code
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=wayland_protocols");
|
||||
|
||||
// Setup cfg aliases
|
||||
cfg_aliases! {
|
||||
// Systems.
|
||||
android_platform: { target_os = "android" },
|
||||
wasm_platform: { target_family = "wasm" },
|
||||
wasm_platform: { target_arch = "wasm32" },
|
||||
macos_platform: { target_os = "macos" },
|
||||
ios_platform: { target_os = "ios" },
|
||||
windows_platform: { target_os = "windows" },
|
||||
@@ -21,4 +48,17 @@ fn main() {
|
||||
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
|
||||
orbital_platform: { redox },
|
||||
}
|
||||
|
||||
// XXX aliases are not available for the build script itself.
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
),
|
||||
feature = "wayland",
|
||||
))]
|
||||
wayland::main();
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
disallowed-methods = [
|
||||
{ path = "web_sys::window", reason = "is not available in every context" },
|
||||
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||
]
|
||||
70
deny.toml
70
deny.toml
@@ -1,70 +0,0 @@
|
||||
# https://embarkstudios.github.io/cargo-deny/
|
||||
# cargo install cargo-deny
|
||||
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
|
||||
# Note: running just `cargo deny check` without a `--target` will result in
|
||||
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
|
||||
targets = [
|
||||
{ triple = "aarch64-apple-ios" },
|
||||
{ triple = "aarch64-linux-android" },
|
||||
{ triple = "i686-pc-windows-gnu" },
|
||||
{ triple = "i686-pc-windows-msvc" },
|
||||
{ triple = "i686-unknown-linux-gnu" },
|
||||
{ triple = "wasm32-unknown-unknown" },
|
||||
{ triple = "x86_64-apple-darwin" },
|
||||
{ triple = "x86_64-apple-ios" },
|
||||
{ triple = "x86_64-pc-windows-gnu" },
|
||||
{ triple = "x86_64-pc-windows-msvc" },
|
||||
{ triple = "x86_64-unknown-linux-gnu" },
|
||||
{ triple = "x86_64-unknown-redox" },
|
||||
]
|
||||
|
||||
|
||||
[advisories]
|
||||
vulnerability = "deny"
|
||||
unmaintained = "warn"
|
||||
yanked = "deny"
|
||||
ignore = []
|
||||
|
||||
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
deny = []
|
||||
skip = [
|
||||
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
||||
{ name = "nix" }, # differing version - as of 2023-03-02 whis can be solved with `cargo update && cargo update -p calloop --precise 0.10.2`
|
||||
{ name = "memoffset"}, # due to different nix versions.
|
||||
{ name = "memmap2" }, # sctk uses a different version until the next update
|
||||
{ name = "libloading" }, # x11rb uses a different version until the next update
|
||||
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 and https://github.com/rustwasm/wasm-bindgen/issues/3390
|
||||
{ name = "num_enum"}, # See above ^, waiting for release
|
||||
{ name = "num_enum_derive"},# See above ^, waiting for release
|
||||
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
|
||||
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
|
||||
]
|
||||
skip-tree = []
|
||||
|
||||
|
||||
[licenses]
|
||||
private = { ignore = true }
|
||||
unlicensed = "deny"
|
||||
allow-osi-fsf-free = "neither"
|
||||
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
|
||||
copyleft = "deny"
|
||||
allow = [
|
||||
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
|
||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
|
||||
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
|
||||
"ISC", # https://tldrlegal.com/license/-isc-license
|
||||
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
|
||||
"MIT-0", # https://choosealicense.com/licenses/mit-0/
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
|
||||
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
|
||||
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
|
||||
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
|
||||
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
||||
]
|
||||
@@ -1,11 +0,0 @@
|
||||
# Image Attribution
|
||||
|
||||
These images are used in the documentation of `winit`.
|
||||
|
||||
## keyboard_*.svg
|
||||
|
||||
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is
|
||||
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
|
||||
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
|
||||
which have been released under the same license as a derivative work.
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB |
@@ -1,7 +1,3 @@
|
||||
#[cfg(any(x11_platform, macos_platform, windows_platform))]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[cfg(any(x11_platform, macos_platform, windows_platform))]
|
||||
fn main() {
|
||||
use std::collections::HashMap;
|
||||
@@ -9,7 +5,7 @@ fn main() {
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
use winit::{
|
||||
dpi::{LogicalPosition, LogicalSize, Position},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
@@ -63,8 +59,8 @@ fn main() {
|
||||
println!("cursor entered in the window {window_id:?}");
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
@@ -74,10 +70,6 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::RedrawRequested(wid) = event {
|
||||
if let Some(window) = windows.get(&wid) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use std::thread;
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time as time;
|
||||
use std::{thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
Wait,
|
||||
@@ -48,7 +40,7 @@ fn main() {
|
||||
let mut close_requested = false;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::StartCause;
|
||||
use winit::event::{ElementState, StartCause, VirtualKeyCode};
|
||||
println!("{event:?}");
|
||||
match event {
|
||||
Event::NewEvents(start_cause) => {
|
||||
@@ -62,33 +54,31 @@ fn main() {
|
||||
close_requested = true;
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
Key::Character("1") => {
|
||||
} => match virtual_code {
|
||||
VirtualKeyCode::Key1 => {
|
||||
mode = Mode::Wait;
|
||||
println!("\nmode: {mode:?}\n");
|
||||
}
|
||||
Key::Character("2") => {
|
||||
VirtualKeyCode::Key2 => {
|
||||
mode = Mode::WaitUntil;
|
||||
println!("\nmode: {mode:?}\n");
|
||||
}
|
||||
Key::Character("3") => {
|
||||
VirtualKeyCode::Key3 => {
|
||||
mode = Mode::Poll;
|
||||
println!("\nmode: {mode:?}\n");
|
||||
}
|
||||
Key::Character("r") => {
|
||||
VirtualKeyCode::R => {
|
||||
request_redraw = !request_redraw;
|
||||
println!("\nrequest_redraw: {request_redraw}\n");
|
||||
}
|
||||
Key::Escape => {
|
||||
VirtualKeyCode::Escape => {
|
||||
close_requested = true;
|
||||
}
|
||||
_ => (),
|
||||
@@ -103,15 +93,13 @@ fn main() {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {}
|
||||
Event::RedrawEventsCleared => {
|
||||
match mode {
|
||||
Mode::Wait => control_flow.set_wait(),
|
||||
Mode::WaitUntil => {
|
||||
if !wait_cancelled {
|
||||
control_flow.set_wait_until(time::Instant::now() + WAIT_TIME);
|
||||
control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
|
||||
}
|
||||
}
|
||||
Mode::Poll => {
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -26,8 +23,8 @@ fn main() {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
@@ -49,9 +46,6 @@ fn main() {
|
||||
} => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
@@ -60,7 +54,8 @@ fn main() {
|
||||
const CURSORS: &[CursorIcon] = &[
|
||||
CursorIcon::Default,
|
||||
CursorIcon::Crosshair,
|
||||
CursorIcon::Pointer,
|
||||
CursorIcon::Hand,
|
||||
CursorIcon::Arrow,
|
||||
CursorIcon::Move,
|
||||
CursorIcon::Text,
|
||||
CursorIcon::Wait,
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, ModifiersState},
|
||||
window::{CursorGrabMode, WindowBuilder},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -29,29 +25,27 @@ fn main() {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(key),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
use winit::event::VirtualKeyCode::*;
|
||||
let result = match key {
|
||||
Key::Escape => {
|
||||
Escape => {
|
||||
control_flow.set_exit();
|
||||
Ok(())
|
||||
}
|
||||
Key::Character(ch) => match ch.to_lowercase().as_str() {
|
||||
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
|
||||
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
|
||||
"a" => window.set_cursor_grab(CursorGrabMode::None),
|
||||
"h" => {
|
||||
window.set_cursor_visible(modifiers.shift_key());
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
},
|
||||
G => window.set_cursor_grab(CursorGrabMode::Confined),
|
||||
L => window.set_cursor_grab(CursorGrabMode::Locked),
|
||||
A => window.set_cursor_grab(CursorGrabMode::None),
|
||||
H => {
|
||||
window.set_cursor_visible(modifiers.shift());
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
@@ -59,7 +53,7 @@ fn main() {
|
||||
println!("error: {err}");
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
|
||||
WindowEvent::ModifiersChanged(m) => modifiers = m,
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
@@ -70,7 +64,6 @@ fn main() {
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => fill::fill_window(&window),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,9 +9,6 @@ fn main() {
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum CustomEvent {
|
||||
Timer,
|
||||
@@ -20,7 +17,7 @@ fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -47,9 +44,6 @@ fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
|
||||
event::{
|
||||
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
|
||||
},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -47,27 +45,20 @@ fn main() {
|
||||
name_windows(entered_id, switched, &window_1, &window_2)
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
logical_key: Key::Character(c),
|
||||
virtual_keycode: Some(VirtualKeyCode::X),
|
||||
..
|
||||
},
|
||||
..
|
||||
} if c == "x" => {
|
||||
} => {
|
||||
switched = !switched;
|
||||
name_windows(entered_id, switched, &window_1, &window_2);
|
||||
println!("Switched!")
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(wid) => {
|
||||
if wid == window_1.id() {
|
||||
fill::fill_window(&window_1);
|
||||
} else if wid == window_2.id() {
|
||||
fill::fill_window(&window_2);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
|
||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::keyboard::Key;
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::WindowExtMacOS;
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -54,81 +50,72 @@ fn main() {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key {
|
||||
Key::Escape => control_flow.set_exit(),
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
Key::Character(ch) => match ch.to_lowercase().as_str() {
|
||||
"f" | "b" if window.fullscreen().is_some() => {
|
||||
window.set_fullscreen(None);
|
||||
} => match virtual_code {
|
||||
VirtualKeyCode::Escape => control_flow.set_exit(),
|
||||
VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => {
|
||||
window.set_fullscreen(None);
|
||||
}
|
||||
VirtualKeyCode::F => {
|
||||
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
|
||||
println!("Setting mode: {fullscreen:?}");
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
VirtualKeyCode::B => {
|
||||
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
|
||||
println!("Setting mode: {fullscreen:?}");
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
VirtualKeyCode::C => {
|
||||
window.set_simple_fullscreen(!window.simple_fullscreen());
|
||||
}
|
||||
VirtualKeyCode::S => {
|
||||
monitor_index += 1;
|
||||
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
|
||||
monitor = mon;
|
||||
} else {
|
||||
monitor_index = 0;
|
||||
monitor = elwt.available_monitors().next().expect("no monitor found!");
|
||||
}
|
||||
"f" => {
|
||||
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
|
||||
println!("Setting mode: {fullscreen:?}");
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
"b" => {
|
||||
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
|
||||
println!("Setting mode: {fullscreen:?}");
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
"c" => {
|
||||
window.set_simple_fullscreen(!window.simple_fullscreen());
|
||||
}
|
||||
"s" => {
|
||||
monitor_index += 1;
|
||||
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
|
||||
monitor = mon;
|
||||
} else {
|
||||
monitor_index = 0;
|
||||
monitor =
|
||||
elwt.available_monitors().next().expect("no monitor found!");
|
||||
}
|
||||
println!("Monitor: {:?}", monitor.name());
|
||||
println!("Monitor: {:?}", monitor.name());
|
||||
|
||||
mode_index = 0;
|
||||
mode = monitor.video_modes().next().expect("no mode found");
|
||||
println!("Mode: {mode}");
|
||||
}
|
||||
VirtualKeyCode::M => {
|
||||
mode_index += 1;
|
||||
if let Some(m) = monitor.video_modes().nth(mode_index) {
|
||||
mode = m;
|
||||
} else {
|
||||
mode_index = 0;
|
||||
mode = monitor.video_modes().next().expect("no mode found");
|
||||
println!("Mode: {mode}");
|
||||
}
|
||||
"m" => {
|
||||
mode_index += 1;
|
||||
if let Some(m) = monitor.video_modes().nth(mode_index) {
|
||||
mode = m;
|
||||
} else {
|
||||
mode_index = 0;
|
||||
mode = monitor.video_modes().next().expect("no mode found");
|
||||
}
|
||||
println!("Mode: {mode}");
|
||||
}
|
||||
"d" => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
}
|
||||
"x" => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
"z" => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
println!("Mode: {mode}");
|
||||
}
|
||||
VirtualKeyCode::D => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
VirtualKeyCode::Z => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,20 +2,16 @@
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("Your faithful window")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -23,6 +19,10 @@ fn main() {
|
||||
let mut close_requested = false;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::{
|
||||
ElementState::Released,
|
||||
VirtualKeyCode::{N, Y},
|
||||
};
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
@@ -46,18 +46,16 @@ fn main() {
|
||||
// the Y key.
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
state: ElementState::Released,
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
match key.as_ref() {
|
||||
Key::Character("y") => {
|
||||
match virtual_code {
|
||||
Y => {
|
||||
if close_requested {
|
||||
// This is where you'll want to do any cleanup you need.
|
||||
println!("Buh-bye!");
|
||||
@@ -70,7 +68,7 @@ fn main() {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
Key::Character("n") => {
|
||||
N => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
@@ -82,9 +80,6 @@ fn main() {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
use log::LevelFilter;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::{ElementState, Event, Ime, WindowEvent},
|
||||
dpi::PhysicalPosition,
|
||||
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::{Key, KeyCode},
|
||||
window::{ImePurpose, WindowBuilder},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new()
|
||||
.with_level(LevelFilter::Trace)
|
||||
@@ -66,7 +62,7 @@ fn main() {
|
||||
);
|
||||
ime_pos = cursor_position;
|
||||
if may_show_ime {
|
||||
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
|
||||
window.set_ime_position(ime_pos);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
@@ -76,21 +72,31 @@ fn main() {
|
||||
println!("{event:?}");
|
||||
may_show_ime = event != Ime::Disabled;
|
||||
if may_show_ime {
|
||||
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
|
||||
window.set_ime_position(ime_pos);
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { event, .. },
|
||||
event: WindowEvent::ReceivedCharacter(ch),
|
||||
..
|
||||
} => {
|
||||
println!("key: {event:?}");
|
||||
println!("ch: {ch:?}");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::KeyboardInput { input, .. },
|
||||
..
|
||||
} => {
|
||||
println!("key: {input:?}");
|
||||
|
||||
if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 {
|
||||
if input.state == ElementState::Pressed
|
||||
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
|
||||
{
|
||||
ime_allowed = !ime_allowed;
|
||||
window.set_ime_allowed(ime_allowed);
|
||||
println!("\nIME allowed: {ime_allowed}\n");
|
||||
}
|
||||
if event.state == ElementState::Pressed && event.logical_key == Key::F3 {
|
||||
if input.state == ElementState::Pressed
|
||||
&& input.virtual_keycode == Some(VirtualKeyCode::F3)
|
||||
{
|
||||
ime_purpose = match ime_purpose {
|
||||
ImePurpose::Normal => ImePurpose::Password,
|
||||
ImePurpose::Password => ImePurpose::Terminal,
|
||||
@@ -100,9 +106,6 @@ fn main() {
|
||||
println!("\nIME purpose: {ime_purpose:?}\n");
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::{Key, ModifiersState},
|
||||
// WARNING: This is not available on all platforms (for example on the web).
|
||||
platform::modifier_supplement::KeyEventExtModifierSupplement,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
fn main() {
|
||||
println!("This example is not supported on this platform");
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
fn main() {
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
simple_logger::SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut modifiers = ModifiersState::default();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::ModifiersChanged(new) => {
|
||||
modifiers = new.state();
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
if event.state == ElementState::Pressed && !event.repeat {
|
||||
match event.key_without_modifiers().as_ref() {
|
||||
Key::Character("1") => {
|
||||
if modifiers.shift_key() {
|
||||
println!("Shift + 1 | logical_key: {:?}", event.logical_key);
|
||||
} else {
|
||||
println!("1");
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -7,9 +7,6 @@ use winit::{
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -59,9 +56,6 @@ In other words, the deltas indicate the direction in which to move the content (
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,9 +7,8 @@ fn main() {
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::{Key, ModifiersState},
|
||||
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
|
||||
};
|
||||
|
||||
@@ -30,7 +29,6 @@ fn main() {
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
window_senders.insert(window.id(), tx);
|
||||
let mut modifiers = ModifiersState::default();
|
||||
thread::spawn(move || {
|
||||
while let Ok(event) = rx.recv() {
|
||||
match event {
|
||||
@@ -53,116 +51,107 @@ fn main() {
|
||||
);
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new) => {
|
||||
modifiers = new.state();
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
logical_key: key,
|
||||
virtual_keycode: Some(key),
|
||||
modifiers,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
use Key::{ArrowLeft, ArrowRight};
|
||||
window.set_title(&format!("{key:?}"));
|
||||
let state = !modifiers.shift_key();
|
||||
let state = !modifiers.shift();
|
||||
use VirtualKeyCode::*;
|
||||
match key {
|
||||
Key1 => window.set_window_level(WindowLevel::AlwaysOnTop),
|
||||
Key2 => window.set_window_level(WindowLevel::AlwaysOnBottom),
|
||||
Key3 => window.set_window_level(WindowLevel::Normal),
|
||||
C => window.set_cursor_icon(match state {
|
||||
true => CursorIcon::Progress,
|
||||
false => CursorIcon::Default,
|
||||
}),
|
||||
D => window.set_decorations(!state),
|
||||
// Cycle through video modes
|
||||
Key::ArrowRight | Key::ArrowLeft => {
|
||||
Right | Left => {
|
||||
video_mode_id = match key {
|
||||
ArrowLeft => video_mode_id.saturating_sub(1),
|
||||
ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||
Left => video_mode_id.saturating_sub(1),
|
||||
Right => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!("Picking video mode: {}", video_modes[video_mode_id]);
|
||||
}
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
Key::Character(ch) => match ch.to_lowercase().as_str() {
|
||||
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
|
||||
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
|
||||
"3" => window.set_window_level(WindowLevel::Normal),
|
||||
"c" => window.set_cursor_icon(match state {
|
||||
true => CursorIcon::Progress,
|
||||
false => CursorIcon::Default,
|
||||
}),
|
||||
"d" => window.set_decorations(!state),
|
||||
"f" => window.set_fullscreen(match (state, modifiers.alt_key()) {
|
||||
(true, false) => Some(Fullscreen::Borderless(None)),
|
||||
(true, true) => Some(Fullscreen::Exclusive(
|
||||
video_modes[video_mode_id].clone(),
|
||||
)),
|
||||
(false, _) => None,
|
||||
}),
|
||||
"l" if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked)
|
||||
{
|
||||
println!("error: {err}");
|
||||
}
|
||||
F => window.set_fullscreen(match (state, modifiers.alt()) {
|
||||
(true, false) => Some(Fullscreen::Borderless(None)),
|
||||
(true, true) => {
|
||||
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
|
||||
}
|
||||
"g" if state => {
|
||||
if let Err(err) =
|
||||
window.set_cursor_grab(CursorGrabMode::Confined)
|
||||
{
|
||||
println!("error: {err}");
|
||||
}
|
||||
(false, _) => None,
|
||||
}),
|
||||
L if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
|
||||
println!("error: {err}");
|
||||
}
|
||||
"g" | "l" if !state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
|
||||
println!("error: {err}");
|
||||
}
|
||||
}
|
||||
G if state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
|
||||
println!("error: {err}");
|
||||
}
|
||||
"h" => window.set_cursor_visible(!state),
|
||||
"i" => {
|
||||
println!("Info:");
|
||||
println!("-> outer_position : {:?}", window.outer_position());
|
||||
println!("-> inner_position : {:?}", window.inner_position());
|
||||
println!("-> outer_size : {:?}", window.outer_size());
|
||||
println!("-> inner_size : {:?}", window.inner_size());
|
||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||
}
|
||||
G | L if !state => {
|
||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
|
||||
println!("error: {err}");
|
||||
}
|
||||
"l" => window.set_min_inner_size(match state {
|
||||
true => Some(WINDOW_SIZE),
|
||||
false => None,
|
||||
}),
|
||||
"m" => window.set_maximized(state),
|
||||
"p" => window.set_outer_position({
|
||||
let mut position = window.outer_position().unwrap();
|
||||
let sign = if state { 1 } else { -1 };
|
||||
position.x += 10 * sign;
|
||||
position.y += 10 * sign;
|
||||
position
|
||||
}),
|
||||
"q" => window.request_redraw(),
|
||||
"r" => window.set_resizable(state),
|
||||
"s" => window.set_inner_size(match state {
|
||||
true => PhysicalSize::new(
|
||||
WINDOW_SIZE.width + 100,
|
||||
WINDOW_SIZE.height + 100,
|
||||
),
|
||||
false => WINDOW_SIZE,
|
||||
}),
|
||||
"w" => {
|
||||
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
||||
window
|
||||
.set_cursor_position(Position::Physical(
|
||||
PhysicalPosition::new(
|
||||
size.width as i32 / 2,
|
||||
size.height as i32 / 2,
|
||||
),
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
H => window.set_cursor_visible(!state),
|
||||
I => {
|
||||
println!("Info:");
|
||||
println!("-> outer_position : {:?}", window.outer_position());
|
||||
println!("-> inner_position : {:?}", window.inner_position());
|
||||
println!("-> outer_size : {:?}", window.outer_size());
|
||||
println!("-> inner_size : {:?}", window.inner_size());
|
||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||
}
|
||||
L => window.set_min_inner_size(match state {
|
||||
true => Some(WINDOW_SIZE),
|
||||
false => None,
|
||||
}),
|
||||
M => window.set_maximized(state),
|
||||
P => window.set_outer_position({
|
||||
let mut position = window.outer_position().unwrap();
|
||||
let sign = if state { 1 } else { -1 };
|
||||
position.x += 10 * sign;
|
||||
position.y += 10 * sign;
|
||||
position
|
||||
}),
|
||||
Q => window.request_redraw(),
|
||||
R => window.set_resizable(state),
|
||||
S => window.set_inner_size(match state {
|
||||
true => PhysicalSize::new(
|
||||
WINDOW_SIZE.width + 100,
|
||||
WINDOW_SIZE.height + 100,
|
||||
),
|
||||
false => WINDOW_SIZE,
|
||||
}),
|
||||
W => {
|
||||
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
||||
window
|
||||
.set_cursor_position(Position::Physical(
|
||||
PhysicalPosition::new(
|
||||
size.width as i32 / 2,
|
||||
size.height as i32 / 2,
|
||||
),
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
"z" => {
|
||||
window.set_visible(false);
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
window.set_visible(true);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
Z => {
|
||||
window.set_visible(false);
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
window.set_visible(true);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -181,10 +170,10 @@ fn main() {
|
||||
WindowEvent::CloseRequested
|
||||
| WindowEvent::Destroyed
|
||||
| WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
logical_key: Key::Escape,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
|
||||
@@ -4,15 +4,11 @@ use std::collections::HashMap;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -43,15 +39,15 @@ fn main() {
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
logical_key: Key::Character(c),
|
||||
virtual_keycode: Some(VirtualKeyCode::N),
|
||||
..
|
||||
},
|
||||
is_synthetic: false,
|
||||
..
|
||||
} if matches!(c.as_ref(), "n" | "N") => {
|
||||
} => {
|
||||
let window = Window::new(event_loop).unwrap();
|
||||
println!("Opened a new window: {:?}", window.id());
|
||||
windows.insert(window.id(), window);
|
||||
@@ -59,11 +55,6 @@ fn main() {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(window_id) => {
|
||||
if let Some(window) = windows.get(&window_id) {
|
||||
fill::fill_window(window);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,9 +7,6 @@ use winit::{
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -37,7 +34,6 @@ fn main() {
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#[cfg(not(wasm_platform))]
|
||||
fn main() {
|
||||
use std::{sync::Arc, thread, time};
|
||||
use std::{thread, time};
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
@@ -11,26 +11,17 @@ fn main() {
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = {
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
Arc::new(window)
|
||||
};
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
thread::spawn({
|
||||
let window = window.clone();
|
||||
move || loop {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
window.request_redraw();
|
||||
}
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
window.request_redraw();
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
@@ -45,7 +36,6 @@ fn main() {
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -3,15 +3,11 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::KeyCode,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -34,9 +30,9 @@ fn main() {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
physical_key: KeyCode::Space,
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
@@ -48,9 +44,6 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::Key,
|
||||
window::{Theme, WindowBuilder},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -45,34 +41,30 @@ fn main() {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("A" | "a") => {
|
||||
} => match key {
|
||||
VirtualKeyCode::A => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(None);
|
||||
}
|
||||
Key::Character("L" | "l") => {
|
||||
VirtualKeyCode::L => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(Some(Theme::Light));
|
||||
}
|
||||
Key::Character("D" | "d") => {
|
||||
VirtualKeyCode::D => {
|
||||
println!("Theme was: {:?}", window.theme());
|
||||
window.set_theme(Some(Theme::Dark));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use instant::Instant;
|
||||
use std::time::Duration;
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time::Instant;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time::Instant;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
@@ -13,14 +10,11 @@ use winit::{
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -42,9 +36,6 @@ fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,14 +5,11 @@ use winit::{
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("Touchpad gestures")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -44,8 +41,6 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::RedrawRequested(_) = event {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,9 +7,6 @@ use winit::{
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -31,9 +28,6 @@ fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
//! Fill the window buffer with a solid color.
|
||||
//!
|
||||
//! Launching a window without drawing to it has unpredictable results varying from platform to
|
||||
//! platform. In order to have well-defined examples, this module provides an easy way to
|
||||
//! fill the window buffer with a solid color.
|
||||
//!
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
use winit::window::Window;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
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 winit::window::WindowId;
|
||||
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
context: Context,
|
||||
|
||||
/// The hash map of window IDs to surfaces.
|
||||
surfaces: HashMap<WindowId, Surface>,
|
||||
}
|
||||
|
||||
impl GraphicsContext {
|
||||
fn new(w: &Window) -> Self {
|
||||
Self {
|
||||
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
|
||||
surfaces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
GC.with(|gc| {
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
let size = window.inner_size();
|
||||
|
||||
surface
|
||||
.resize(
|
||||
NonZeroU32::new(size.width).expect("Width must be greater than zero"),
|
||||
NonZeroU32::new(size.height).expect("Height must be greater than zero"),
|
||||
)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub(super) fn fill_window(_window: &Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
#![allow(clippy::disallowed_methods, clippy::single_match)]
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::KeyCode,
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
@@ -32,25 +31,6 @@ pub fn main() {
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
physical_key: KeyCode::KeyF,
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
} if window_id == window.id() => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
@@ -72,17 +52,14 @@ mod wasm {
|
||||
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
let canvas = window.canvas().unwrap();
|
||||
let canvas = window.canvas();
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
|
||||
// Set a background color for the canvas to make it easier to tell where the canvas is for debugging purposes.
|
||||
canvas
|
||||
.style()
|
||||
.set_property("background-color", "crimson")
|
||||
.unwrap();
|
||||
canvas.style().set_css_text("background-color: crimson;");
|
||||
body.append_child(&canvas).unwrap();
|
||||
|
||||
let log_header = document.create_element("h2").unwrap();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(clippy::disallowed_methods)]
|
||||
|
||||
pub fn main() {
|
||||
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
|
||||
}
|
||||
@@ -68,7 +66,7 @@ This example demonstrates the desired future functionality which will possibly b
|
||||
let body = document.body().unwrap();
|
||||
|
||||
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
|
||||
let canvas = window.canvas().unwrap();
|
||||
let canvas = window.canvas();
|
||||
canvas
|
||||
.style()
|
||||
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
|
||||
|
||||
@@ -7,9 +7,6 @@ use winit::{
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -32,9 +29,6 @@ fn main() {
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,15 +5,11 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoop},
|
||||
keyboard::Key,
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{DeviceEventFilter, EventLoop},
|
||||
window::{WindowBuilder, WindowButtons},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -29,7 +25,7 @@ fn main() {
|
||||
eprintln!(" (G) Toggle maximize button");
|
||||
eprintln!(" (H) Toggle minimize button");
|
||||
|
||||
event_loop.listen_device_events(DeviceEvents::Always);
|
||||
event_loop.set_device_event_filter(DeviceEventFilter::Never);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
@@ -38,25 +34,25 @@ fn main() {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: key,
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key.as_ref() {
|
||||
Key::Character("F" | "f") => {
|
||||
} => match key {
|
||||
VirtualKeyCode::F => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
|
||||
}
|
||||
Key::Character("G" | "g") => {
|
||||
VirtualKeyCode::G => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
|
||||
}
|
||||
Key::Character("H" | "h") => {
|
||||
VirtualKeyCode::H => {
|
||||
let buttons = window.enabled_buttons();
|
||||
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
|
||||
}
|
||||
@@ -66,9 +62,6 @@ fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,15 +5,11 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::{LogicalSize, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoop},
|
||||
keyboard::{Key, KeyCode},
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{DeviceEventFilter, EventLoop},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -36,31 +32,29 @@ fn main() {
|
||||
let mut minimized = false;
|
||||
let mut visible = true;
|
||||
|
||||
event_loop.listen_device_events(DeviceEvents::Always);
|
||||
event_loop.set_device_event_filter(DeviceEventFilter::Never);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
// This used to use the virtual key, but the new API
|
||||
// only provides the `physical_key` (`Code`).
|
||||
Event::DeviceEvent {
|
||||
event:
|
||||
DeviceEvent::Key(RawKeyEvent {
|
||||
physical_key,
|
||||
state: ElementState::Released,
|
||||
DeviceEvent::Key(KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
}),
|
||||
..
|
||||
} => match physical_key {
|
||||
KeyCode::KeyM => {
|
||||
} => match key {
|
||||
VirtualKeyCode::M => {
|
||||
if minimized {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
window.focus_window();
|
||||
}
|
||||
}
|
||||
KeyCode::KeyV => {
|
||||
VirtualKeyCode::V => {
|
||||
if !visible {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
@@ -71,19 +65,17 @@ fn main() {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: Key::Character(key_str),
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match key_str.as_ref() {
|
||||
// WARNING: Consider using `key_without_modifers()` if available on your platform.
|
||||
// See the `key_binding` example
|
||||
"e" => {
|
||||
} => match key {
|
||||
VirtualKeyCode::E => {
|
||||
fn area(size: PhysicalSize<u32>) -> u32 {
|
||||
size.width * size.height
|
||||
}
|
||||
@@ -98,7 +90,7 @@ fn main() {
|
||||
eprintln!("no video modes available");
|
||||
}
|
||||
}
|
||||
"f" => {
|
||||
VirtualKeyCode::F => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
@@ -106,25 +98,25 @@ fn main() {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
|
||||
}
|
||||
}
|
||||
"p" => {
|
||||
VirtualKeyCode::P => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||
}
|
||||
}
|
||||
"m" => {
|
||||
VirtualKeyCode::M => {
|
||||
minimized = !minimized;
|
||||
window.set_minimized(minimized);
|
||||
}
|
||||
"q" => {
|
||||
VirtualKeyCode::Q => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
"v" => {
|
||||
VirtualKeyCode::V => {
|
||||
visible = !visible;
|
||||
window.set_visible(visible);
|
||||
}
|
||||
"x" => {
|
||||
VirtualKeyCode::X => {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
@@ -134,9 +126,6 @@ fn main() {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
|
||||
event::{
|
||||
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
|
||||
},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::Key,
|
||||
window::{CursorIcon, ResizeDirection, WindowBuilder},
|
||||
};
|
||||
|
||||
const BORDER: f64 = 8.0;
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -55,22 +53,19 @@ fn main() {
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
logical_key: Key::Character(c),
|
||||
virtual_keycode: Some(VirtualKeyCode::B),
|
||||
..
|
||||
},
|
||||
..
|
||||
} if matches!(c.as_ref(), "B" | "b") => {
|
||||
} => {
|
||||
border = !border;
|
||||
window.set_decorations(border);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ use winit::{
|
||||
window::{Icon, WindowBuilder},
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
|
||||
@@ -45,8 +42,6 @@ fn main() {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Event::RedrawRequested(_) = event {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ use winit::{
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
/// Prints the keyboard events characters received when option_is_alt is true versus false.
|
||||
/// A left mouse click will toggle option_is_alt.
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -27,8 +23,6 @@ fn main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
window.set_ime_allowed(true);
|
||||
|
||||
let mut option_as_alt = window.option_as_alt();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
@@ -55,15 +49,13 @@ fn main() {
|
||||
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
|
||||
window.set_option_as_alt(option_as_alt);
|
||||
}
|
||||
WindowEvent::ReceivedCharacter(c) => println!("ReceivedCharacter: {c:?}"),
|
||||
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,15 +2,11 @@ use log::debug;
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyEvent, WindowEvent},
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
keyboard::Key,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
@@ -35,9 +31,9 @@ fn main() {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
event:
|
||||
KeyEvent {
|
||||
logical_key: Key::Space,
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
@@ -55,9 +51,6 @@ fn main() {
|
||||
window.set_resize_increments(new_increments);
|
||||
}
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,14 +19,10 @@ fn main() {
|
||||
platform::run_return::EventLoopExtRunReturn,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
@@ -52,9 +48,6 @@ fn main() {
|
||||
Event::MainEventsCleared => {
|
||||
control_flow.set_exit();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -167,7 +167,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
|
||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
||||
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
|
||||
/// implementation is provided which does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalPosition<P> {
|
||||
pub x: P,
|
||||
@@ -246,7 +246,7 @@ impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
|
||||
}
|
||||
|
||||
/// A position represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalPosition<P> {
|
||||
pub x: P,
|
||||
@@ -325,7 +325,7 @@ impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
|
||||
}
|
||||
|
||||
/// A size represented in logical pixels.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalSize<P> {
|
||||
pub width: P,
|
||||
@@ -407,7 +407,7 @@ impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
|
||||
}
|
||||
|
||||
/// A size represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalSize<P> {
|
||||
pub width: P,
|
||||
|
||||
624
src/event.rs
624
src/event.rs
@@ -34,18 +34,13 @@
|
||||
//!
|
||||
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
|
||||
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
use smol_str::SmolStr;
|
||||
use instant::Instant;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time::Instant;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time::Instant;
|
||||
|
||||
#[cfg(doc)]
|
||||
use crate::window::Window;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
|
||||
platform_impl,
|
||||
window::{Theme, WindowId},
|
||||
};
|
||||
@@ -125,18 +120,6 @@ pub enum Event<'a, T: 'static> {
|
||||
/// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive
|
||||
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
|
||||
///
|
||||
/// ## Web
|
||||
///
|
||||
/// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event
|
||||
/// with the property [`persisted`] being true, which means that the page is being
|
||||
/// put in the [´bfcache`] (back/forward cache) - an in-memory cache that stores a
|
||||
/// complete snapshot of a page (including the JavaScript heap) as the user is
|
||||
/// navigating away.
|
||||
///
|
||||
/// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
|
||||
/// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted
|
||||
/// [`bfcache`]: https://web.dev/bfcache/
|
||||
///
|
||||
/// [`Resumed`]: Self::Resumed
|
||||
Suspended,
|
||||
|
||||
@@ -191,18 +174,6 @@ pub enum Event<'a, T: 'static> {
|
||||
/// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive
|
||||
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
|
||||
///
|
||||
/// ## Web
|
||||
///
|
||||
/// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event
|
||||
/// with the property [`persisted`] being true, which means that the page is being
|
||||
/// restored from the [´bfcache`] (back/forward cache) - an in-memory cache that
|
||||
/// stores a complete snapshot of a page (including the JavaScript heap) as the
|
||||
/// user is navigating away.
|
||||
///
|
||||
/// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
|
||||
/// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted
|
||||
/// [`bfcache`]: https://web.dev/bfcache/
|
||||
///
|
||||
/// [`Suspended`]: Self::Suspended
|
||||
Resumed,
|
||||
|
||||
@@ -256,7 +227,7 @@ pub enum Event<'a, T: 'static> {
|
||||
/// Emitted when the event loop is being shut down.
|
||||
///
|
||||
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
|
||||
/// gets emitted. You generally want to treat this as a "do on quit" event.
|
||||
/// gets emitted. You generally want to treat this as an "do on quit" event.
|
||||
LoopDestroyed,
|
||||
}
|
||||
|
||||
@@ -285,7 +256,6 @@ impl<T: Clone> Clone for Event<'static, T> {
|
||||
}
|
||||
|
||||
impl<'a, T> Event<'a, T> {
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
|
||||
use self::Event::*;
|
||||
match self {
|
||||
@@ -390,21 +360,20 @@ pub enum WindowEvent<'a> {
|
||||
/// hovered.
|
||||
HoveredFileCancelled,
|
||||
|
||||
/// The window received a unicode character.
|
||||
///
|
||||
/// See also the [`Ime`](Self::Ime) event for more complex character sequences.
|
||||
ReceivedCharacter(char),
|
||||
|
||||
/// The window gained or lost focus.
|
||||
///
|
||||
/// The parameter is true if the window has gained focus, and false if it has lost focus.
|
||||
Focused(bool),
|
||||
|
||||
/// An event from the keyboard has been received.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down,
|
||||
/// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key
|
||||
/// events which are not marked as `is_synthetic`.
|
||||
KeyboardInput {
|
||||
device_id: DeviceId,
|
||||
event: KeyEvent,
|
||||
|
||||
input: KeyboardInput,
|
||||
/// If `true`, the event was generated synthetically by winit
|
||||
/// in one of the following circumstances:
|
||||
///
|
||||
@@ -418,7 +387,12 @@ pub enum WindowEvent<'a> {
|
||||
},
|
||||
|
||||
/// The keyboard modifiers have changed.
|
||||
ModifiersChanged(Modifiers),
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** This API is currently unimplemented on the web. This isn't by design - it's an
|
||||
/// issue, and it should get fixed - but it's the current state of the API.
|
||||
ModifiersChanged(ModifiersState),
|
||||
|
||||
/// An event from an input method.
|
||||
///
|
||||
@@ -437,6 +411,8 @@ pub enum WindowEvent<'a> {
|
||||
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
|
||||
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
|
||||
position: PhysicalPosition<f64>,
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// The cursor has entered the window.
|
||||
@@ -450,6 +426,8 @@ pub enum WindowEvent<'a> {
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// An mouse button press has been received.
|
||||
@@ -457,6 +435,8 @@ pub enum WindowEvent<'a> {
|
||||
device_id: DeviceId,
|
||||
state: ElementState,
|
||||
button: MouseButton,
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// Touchpad magnification event with two-finger pinch gesture.
|
||||
@@ -580,24 +560,28 @@ impl Clone for WindowEvent<'static> {
|
||||
DroppedFile(file) => DroppedFile(file.clone()),
|
||||
HoveredFile(file) => HoveredFile(file.clone()),
|
||||
HoveredFileCancelled => HoveredFileCancelled,
|
||||
ReceivedCharacter(c) => ReceivedCharacter(*c),
|
||||
Focused(f) => Focused(*f),
|
||||
KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
input,
|
||||
is_synthetic,
|
||||
} => KeyboardInput {
|
||||
device_id: *device_id,
|
||||
event: event.clone(),
|
||||
input: *input,
|
||||
is_synthetic: *is_synthetic,
|
||||
},
|
||||
Ime(preedit_state) => Ime(preedit_state.clone()),
|
||||
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
|
||||
#[allow(deprecated)]
|
||||
CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
modifiers,
|
||||
} => CursorMoved {
|
||||
device_id: *device_id,
|
||||
position: *position,
|
||||
modifiers: *modifiers,
|
||||
},
|
||||
CursorEntered { device_id } => CursorEntered {
|
||||
device_id: *device_id,
|
||||
@@ -605,23 +589,29 @@ impl Clone for WindowEvent<'static> {
|
||||
CursorLeft { device_id } => CursorLeft {
|
||||
device_id: *device_id,
|
||||
},
|
||||
#[allow(deprecated)]
|
||||
MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
modifiers,
|
||||
} => MouseWheel {
|
||||
device_id: *device_id,
|
||||
delta: *delta,
|
||||
phase: *phase,
|
||||
modifiers: *modifiers,
|
||||
},
|
||||
#[allow(deprecated)]
|
||||
MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
modifiers,
|
||||
} => MouseInput {
|
||||
device_id: *device_id,
|
||||
state: *state,
|
||||
button: *button,
|
||||
modifiers: *modifiers,
|
||||
},
|
||||
TouchpadMagnify {
|
||||
device_id,
|
||||
@@ -683,44 +673,54 @@ impl<'a> WindowEvent<'a> {
|
||||
DroppedFile(file) => Some(DroppedFile(file)),
|
||||
HoveredFile(file) => Some(HoveredFile(file)),
|
||||
HoveredFileCancelled => Some(HoveredFileCancelled),
|
||||
ReceivedCharacter(c) => Some(ReceivedCharacter(c)),
|
||||
Focused(focused) => Some(Focused(focused)),
|
||||
KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
input,
|
||||
is_synthetic,
|
||||
} => Some(KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
input,
|
||||
is_synthetic,
|
||||
}),
|
||||
ModifiersChanged(modifers) => Some(ModifiersChanged(modifers)),
|
||||
ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)),
|
||||
Ime(event) => Some(Ime(event)),
|
||||
#[allow(deprecated)]
|
||||
CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
modifiers,
|
||||
} => Some(CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
modifiers,
|
||||
}),
|
||||
CursorEntered { device_id } => Some(CursorEntered { device_id }),
|
||||
CursorLeft { device_id } => Some(CursorLeft { device_id }),
|
||||
#[allow(deprecated)]
|
||||
MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
modifiers,
|
||||
} => Some(MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
modifiers,
|
||||
}),
|
||||
#[allow(deprecated)]
|
||||
MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
modifiers,
|
||||
} => Some(MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
modifiers,
|
||||
}),
|
||||
TouchpadMagnify {
|
||||
device_id,
|
||||
@@ -831,224 +831,50 @@ pub enum DeviceEvent {
|
||||
state: ElementState,
|
||||
},
|
||||
|
||||
Key(RawKeyEvent),
|
||||
Key(KeyboardInput),
|
||||
|
||||
Text {
|
||||
codepoint: char,
|
||||
},
|
||||
}
|
||||
|
||||
/// Describes a keyboard input as a raw device event.
|
||||
///
|
||||
/// Note that holding down a key may produce repeated `RawKeyEvent`s. The
|
||||
/// operating system doesn't provide information whether such an event is a
|
||||
/// repeat or the initial keypress. An application may emulate this by, for
|
||||
/// example keeping a Map/Set of pressed keys and determining whether a keypress
|
||||
/// corresponds to an already pressed key.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
/// Describes a keyboard input event.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct RawKeyEvent {
|
||||
pub physical_key: keyboard::KeyCode,
|
||||
pub state: ElementState,
|
||||
}
|
||||
pub struct KeyboardInput {
|
||||
/// Identifies the physical key pressed
|
||||
///
|
||||
/// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the
|
||||
/// key is more important than the key's host GUI semantics, such as for movement controls in a first-person
|
||||
/// game.
|
||||
pub scancode: ScanCode,
|
||||
|
||||
/// Describes a keyboard input targeting a window.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEvent {
|
||||
/// Represents the position of a key independent of the currently active layout.
|
||||
///
|
||||
/// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
|
||||
/// The most prevalent use case for this is games. For example the default keys for the player
|
||||
/// to move around might be the W, A, S, and D keys on a US layout. The position of these keys
|
||||
/// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY"
|
||||
/// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.)
|
||||
///
|
||||
/// ## Caveats
|
||||
///
|
||||
/// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that
|
||||
/// implements DVORAK in hardware (or firmware)
|
||||
/// - Your application will likely have to handle keyboards which are missing keys that your
|
||||
/// own keyboard has.
|
||||
/// - Certain `KeyCode`s will move between a couple of different positions depending on what
|
||||
/// layout the keyboard was manufactured to support.
|
||||
///
|
||||
/// **Because of these caveats, it is important that you provide users with a way to configure
|
||||
/// most (if not all) keybinds in your application.**
|
||||
///
|
||||
/// ## `Fn` and `FnLock`
|
||||
///
|
||||
/// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys
|
||||
/// are usually handled at the hardware or OS level, and aren't surfaced to applications. If
|
||||
/// you somehow see this in the wild, we'd like to know :)
|
||||
pub physical_key: keyboard::KeyCode,
|
||||
|
||||
// Allowing `broken_intra_doc_links` for `logical_key`, because
|
||||
// `key_without_modifiers` is not available on all platforms
|
||||
#[cfg_attr(
|
||||
not(any(target_os = "macos", target_os = "windows", target_os = "linux")),
|
||||
allow(rustdoc::broken_intra_doc_links)
|
||||
)]
|
||||
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
|
||||
///
|
||||
/// This has two use cases:
|
||||
/// - Allows querying whether the current input is a Dead key.
|
||||
/// - Allows handling key-bindings on platforms which don't
|
||||
/// support [`key_without_modifiers`].
|
||||
///
|
||||
/// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard
|
||||
/// shortcuts, **it is important that you provide users with a way to configure your
|
||||
/// application's shortcuts so you don't render your application unusable for users with an
|
||||
/// incompatible keyboard layout.**
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Web:** Dead keys might be reported as the real key instead
|
||||
/// of `Dead` depending on the browser/OS.
|
||||
///
|
||||
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
|
||||
pub logical_key: keyboard::Key,
|
||||
|
||||
/// Contains the text produced by this keypress.
|
||||
///
|
||||
/// In most cases this is identical to the content
|
||||
/// of the `Character` variant of `logical_key`.
|
||||
/// However, on Windows when a dead key was pressed earlier
|
||||
/// but cannot be combined with the character from this
|
||||
/// keypress, the produced text will consist of two characters:
|
||||
/// the dead-key-character followed by the character resulting
|
||||
/// from this keypress.
|
||||
///
|
||||
/// An additional difference from `logical_key` is that
|
||||
/// this field stores the text representation of any key
|
||||
/// that has such a representation. For example when
|
||||
/// `logical_key` is `Key::Enter`, this field is `Some("\r")`.
|
||||
///
|
||||
/// This is `None` if the current keypress cannot
|
||||
/// be interpreted as text.
|
||||
///
|
||||
/// See also: `text_with_all_modifiers()`
|
||||
pub text: Option<SmolStr>,
|
||||
|
||||
/// Contains the location of this key on the keyboard.
|
||||
///
|
||||
/// Certain keys on the keyboard may appear in more than once place. For example, the "Shift" key
|
||||
/// appears on the left side of the QWERTY keyboard as well as the right side. However, both keys
|
||||
/// have the same symbolic value. Another example of this phenomenon is the "1" key, which appears
|
||||
/// both above the "Q" key and as the "Keypad 1" key.
|
||||
///
|
||||
/// This field allows the user to differentiate between keys like this that have the same symbolic
|
||||
/// value but different locations on the keyboard.
|
||||
///
|
||||
/// See the [`KeyLocation`] type for more details.
|
||||
///
|
||||
/// [`KeyLocation`]: crate::keyboard::KeyLocation
|
||||
pub location: keyboard::KeyLocation,
|
||||
|
||||
/// Whether the key is being pressed or released.
|
||||
///
|
||||
/// See the [`ElementState`] type for more details.
|
||||
pub state: ElementState,
|
||||
|
||||
/// Whether or not this key is a key repeat event.
|
||||
/// Identifies the semantic meaning of the key
|
||||
///
|
||||
/// On some systems, holding down a key for some period of time causes that key to be repeated
|
||||
/// as though it were being pressed and released repeatedly. This field is `true` if and only if
|
||||
/// this event is the result of one of those repeats.
|
||||
pub repeat: bool,
|
||||
/// Use when the semantics of the key are more important than the physical location of the key, such as when
|
||||
/// implementing appropriate behavior for "page up."
|
||||
pub virtual_keycode: Option<VirtualKeyCode>,
|
||||
|
||||
/// Platform-specific key event information.
|
||||
/// Modifier keys active at the time of this input.
|
||||
///
|
||||
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with all
|
||||
/// modifiers applied.
|
||||
///
|
||||
/// On Android, iOS, Redox and Web, this type is a no-op.
|
||||
pub(crate) platform_specific: platform_impl::KeyEventExtra,
|
||||
}
|
||||
|
||||
/// Describes keyboard modifiers event.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Modifiers {
|
||||
pub(crate) state: ModifiersState,
|
||||
|
||||
// NOTE: Currently pressed modifiers keys.
|
||||
//
|
||||
// The field providing a metadata, it shouldn't be used as a source of truth.
|
||||
pub(crate) pressed_mods: ModifiersKeys,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
/// The state of the modifiers.
|
||||
pub fn state(&self) -> ModifiersState {
|
||||
self.state
|
||||
}
|
||||
|
||||
/// The state of the left shift key.
|
||||
pub fn lshift_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::LSHIFT)
|
||||
}
|
||||
|
||||
/// The state of the right shift key.
|
||||
pub fn rshift_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::RSHIFT)
|
||||
}
|
||||
|
||||
/// The state of the left alt key.
|
||||
pub fn lalt_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::LALT)
|
||||
}
|
||||
|
||||
/// The state of the right alt key.
|
||||
pub fn ralt_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::RALT)
|
||||
}
|
||||
|
||||
/// The state of the left control key.
|
||||
pub fn lcontrol_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::LCONTROL)
|
||||
}
|
||||
|
||||
/// The state of the right control key.
|
||||
pub fn rcontrol_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::RCONTROL)
|
||||
}
|
||||
|
||||
/// The state of the left super key.
|
||||
pub fn lsuper_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::LSUPER)
|
||||
}
|
||||
|
||||
/// The state of the right super key.
|
||||
pub fn rsuper_state(&self) -> ModifiersKeyState {
|
||||
self.mod_state(ModifiersKeys::RSUPER)
|
||||
}
|
||||
|
||||
fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState {
|
||||
if self.pressed_mods.contains(modifier) {
|
||||
ModifiersKeyState::Pressed
|
||||
} else {
|
||||
ModifiersKeyState::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModifiersState> for Modifiers {
|
||||
fn from(value: ModifiersState) -> Self {
|
||||
Self {
|
||||
state: value,
|
||||
pressed_mods: Default::default(),
|
||||
}
|
||||
}
|
||||
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
|
||||
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
pub modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
|
||||
///
|
||||
/// This is also called a "composition event".
|
||||
///
|
||||
/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::KeyboardInput`].
|
||||
/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::ReceivedCharacter`].
|
||||
/// However, one couldn't possibly have a key for every single unicode character that the user might want to type
|
||||
/// - so the solution operating systems employ is to allow the user to type these using _a sequence of keypresses_ instead.
|
||||
///
|
||||
/// A prominent example of this is accents - many keyboard layouts allow you to first click the "accent key", and then
|
||||
/// the character you want to apply the accent to. In this case, some platforms will generate the following event sequence:
|
||||
/// the character you want to apply the accent to. This will generate the following event sequence:
|
||||
/// ```ignore
|
||||
/// // Press "`" key
|
||||
/// Ime::Preedit("`", Some((0, 0)))
|
||||
@@ -1058,9 +884,9 @@ impl From<ModifiersState> for Modifiers {
|
||||
/// ```
|
||||
///
|
||||
/// Additionally, certain input devices are configured to display a candidate box that allow the user to select the
|
||||
/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_cursor_area`].)
|
||||
/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_position`].)
|
||||
///
|
||||
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the following event
|
||||
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keybaord the following event
|
||||
/// sequence could be obtained:
|
||||
/// ```ignore
|
||||
/// // Press "A" key
|
||||
@@ -1082,7 +908,7 @@ pub enum Ime {
|
||||
///
|
||||
/// After getting this event you could receive [`Preedit`](Self::Preedit) and
|
||||
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests
|
||||
/// like [`Window::set_ime_cursor_area`].
|
||||
/// like [`Window::set_ime_position`].
|
||||
Enabled,
|
||||
|
||||
/// Notifies when a new composing text should be set at the cursor position.
|
||||
@@ -1103,7 +929,7 @@ pub enum Ime {
|
||||
///
|
||||
/// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or
|
||||
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should
|
||||
/// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending
|
||||
/// also stop issuing IME related requests like [`Window::set_ime_position`] and clear pending
|
||||
/// preedit text.
|
||||
Disabled,
|
||||
}
|
||||
@@ -1211,6 +1037,9 @@ impl Force {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hardware-dependent keyboard scan code.
|
||||
pub type ScanCode = u32;
|
||||
|
||||
/// Identifier for a specific analog axis on some device.
|
||||
pub type AxisId = u32;
|
||||
|
||||
@@ -1226,19 +1055,12 @@ pub enum ElementState {
|
||||
}
|
||||
|
||||
/// Describes a button of a mouse controller.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **macOS:** `Back` and `Forward` might not work with all hardware.
|
||||
/// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Back,
|
||||
Forward,
|
||||
Other(u16),
|
||||
}
|
||||
|
||||
@@ -1268,3 +1090,303 @@ pub enum MouseScrollDelta {
|
||||
/// and move the content right and down (to reveal more things left and up).
|
||||
PixelDelta(PhysicalPosition<f64>),
|
||||
}
|
||||
|
||||
/// Symbolic name for a keyboard key.
|
||||
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
||||
#[repr(u32)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum VirtualKeyCode {
|
||||
/// The '1' key over the letters.
|
||||
Key1,
|
||||
/// The '2' key over the letters.
|
||||
Key2,
|
||||
/// The '3' key over the letters.
|
||||
Key3,
|
||||
/// The '4' key over the letters.
|
||||
Key4,
|
||||
/// The '5' key over the letters.
|
||||
Key5,
|
||||
/// The '6' key over the letters.
|
||||
Key6,
|
||||
/// The '7' key over the letters.
|
||||
Key7,
|
||||
/// The '8' key over the letters.
|
||||
Key8,
|
||||
/// The '9' key over the letters.
|
||||
Key9,
|
||||
/// The '0' key over the 'O' and 'P' keys.
|
||||
Key0,
|
||||
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
|
||||
/// The Escape key, next to F1.
|
||||
Escape,
|
||||
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
|
||||
/// Print Screen/SysRq.
|
||||
Snapshot,
|
||||
/// Scroll Lock.
|
||||
Scroll,
|
||||
/// Pause/Break key, next to Scroll lock.
|
||||
Pause,
|
||||
|
||||
/// `Insert`, next to Backspace.
|
||||
Insert,
|
||||
Home,
|
||||
Delete,
|
||||
End,
|
||||
PageDown,
|
||||
PageUp,
|
||||
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
|
||||
/// The Backspace key, right over Enter.
|
||||
// TODO: rename
|
||||
Back,
|
||||
/// The Enter key.
|
||||
Return,
|
||||
/// The space bar.
|
||||
Space,
|
||||
|
||||
/// The "Compose" key on Linux.
|
||||
Compose,
|
||||
|
||||
Caret,
|
||||
|
||||
Numlock,
|
||||
Numpad0,
|
||||
Numpad1,
|
||||
Numpad2,
|
||||
Numpad3,
|
||||
Numpad4,
|
||||
Numpad5,
|
||||
Numpad6,
|
||||
Numpad7,
|
||||
Numpad8,
|
||||
Numpad9,
|
||||
NumpadAdd,
|
||||
NumpadDivide,
|
||||
NumpadDecimal,
|
||||
NumpadComma,
|
||||
NumpadEnter,
|
||||
NumpadEquals,
|
||||
NumpadMultiply,
|
||||
NumpadSubtract,
|
||||
|
||||
AbntC1,
|
||||
AbntC2,
|
||||
Apostrophe,
|
||||
Apps,
|
||||
Asterisk,
|
||||
At,
|
||||
Ax,
|
||||
Backslash,
|
||||
Calculator,
|
||||
Capital,
|
||||
Colon,
|
||||
Comma,
|
||||
Convert,
|
||||
Equals,
|
||||
Grave,
|
||||
Kana,
|
||||
Kanji,
|
||||
LAlt,
|
||||
LBracket,
|
||||
LControl,
|
||||
LShift,
|
||||
LWin,
|
||||
Mail,
|
||||
MediaSelect,
|
||||
MediaStop,
|
||||
Minus,
|
||||
Mute,
|
||||
MyComputer,
|
||||
// also called "Next"
|
||||
NavigateForward,
|
||||
// also called "Prior"
|
||||
NavigateBackward,
|
||||
NextTrack,
|
||||
NoConvert,
|
||||
OEM102,
|
||||
Period,
|
||||
PlayPause,
|
||||
Plus,
|
||||
Power,
|
||||
PrevTrack,
|
||||
RAlt,
|
||||
RBracket,
|
||||
RControl,
|
||||
RShift,
|
||||
RWin,
|
||||
Semicolon,
|
||||
Slash,
|
||||
Sleep,
|
||||
Stop,
|
||||
Sysrq,
|
||||
Tab,
|
||||
Underline,
|
||||
Unlabeled,
|
||||
VolumeDown,
|
||||
VolumeUp,
|
||||
Wake,
|
||||
WebBack,
|
||||
WebFavorites,
|
||||
WebForward,
|
||||
WebHome,
|
||||
WebRefresh,
|
||||
WebSearch,
|
||||
WebStop,
|
||||
Yen,
|
||||
Copy,
|
||||
Paste,
|
||||
Cut,
|
||||
}
|
||||
|
||||
impl ModifiersState {
|
||||
/// Returns `true` if the shift key is pressed.
|
||||
pub fn shift(&self) -> bool {
|
||||
self.intersects(Self::SHIFT)
|
||||
}
|
||||
/// Returns `true` if the control key is pressed.
|
||||
pub fn ctrl(&self) -> bool {
|
||||
self.intersects(Self::CTRL)
|
||||
}
|
||||
/// Returns `true` if the alt key is pressed.
|
||||
pub fn alt(&self) -> bool {
|
||||
self.intersects(Self::ALT)
|
||||
}
|
||||
/// Returns `true` if the logo key is pressed.
|
||||
pub fn logo(&self) -> bool {
|
||||
self.intersects(Self::LOGO)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each flag represents a modifier and is set if this modifier is active.
|
||||
#[derive(Default)]
|
||||
pub struct ModifiersState: u32 {
|
||||
// left and right modifiers are currently commented out, but we should be able to support
|
||||
// them in a future release
|
||||
/// The "shift" key.
|
||||
const SHIFT = 0b100;
|
||||
// const LSHIFT = 0b010;
|
||||
// const RSHIFT = 0b001;
|
||||
/// The "control" key.
|
||||
const CTRL = 0b100 << 3;
|
||||
// const LCTRL = 0b010 << 3;
|
||||
// const RCTRL = 0b001 << 3;
|
||||
/// The "alt" key.
|
||||
const ALT = 0b100 << 6;
|
||||
// const LALT = 0b010 << 6;
|
||||
// const RALT = 0b001 << 6;
|
||||
/// This is the "windows" key on PC and "command" key on Mac.
|
||||
const LOGO = 0b100 << 9;
|
||||
// const LLOGO = 0b010 << 9;
|
||||
// const RLOGO = 0b001 << 9;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod modifiers_serde {
|
||||
use super::ModifiersState;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[serde(rename = "ModifiersState")]
|
||||
pub struct ModifiersStateSerialize {
|
||||
pub shift: bool,
|
||||
pub ctrl: bool,
|
||||
pub alt: bool,
|
||||
pub logo: bool,
|
||||
}
|
||||
|
||||
impl Serialize for ModifiersState {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let s = ModifiersStateSerialize {
|
||||
shift: self.shift(),
|
||||
ctrl: self.ctrl(),
|
||||
alt: self.alt(),
|
||||
logo: self.logo(),
|
||||
};
|
||||
s.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ModifiersState {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let ModifiersStateSerialize {
|
||||
shift,
|
||||
ctrl,
|
||||
alt,
|
||||
logo,
|
||||
} = ModifiersStateSerialize::deserialize(deserializer)?;
|
||||
let mut m = ModifiersState::empty();
|
||||
m.set(ModifiersState::SHIFT, shift);
|
||||
m.set(ModifiersState::CTRL, ctrl);
|
||||
m.set(ModifiersState::ALT, alt);
|
||||
m.set(ModifiersState::LOGO, logo);
|
||||
Ok(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,11 @@
|
||||
//! handle events.
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{error, fmt};
|
||||
|
||||
use instant::{Duration, Instant};
|
||||
use once_cell::sync::OnceCell;
|
||||
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time::{Duration, Instant};
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time::{Duration, Instant};
|
||||
|
||||
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
|
||||
|
||||
@@ -69,8 +66,6 @@ impl EventLoopBuilder<()> {
|
||||
}
|
||||
}
|
||||
|
||||
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
impl<T> EventLoopBuilder<T> {
|
||||
/// Start building a new event loop, with the given type as the user event
|
||||
/// type.
|
||||
@@ -116,10 +111,10 @@ impl<T> EventLoopBuilder<T> {
|
||||
)]
|
||||
#[inline]
|
||||
pub fn build(&mut self) -> EventLoop<T> {
|
||||
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
|
||||
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
|
||||
if EVENT_LOOP_CREATED.set(()).is_err() {
|
||||
panic!("Creating EventLoop multiple times is not supported.");
|
||||
}
|
||||
|
||||
// Certain platforms accept a mutable reference in their API.
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
EventLoop {
|
||||
@@ -127,11 +122,6 @@ impl<T> EventLoopBuilder<T> {
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(wasm_platform)]
|
||||
pub(crate) fn allow_event_loop_recreation() {
|
||||
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoop<T> {
|
||||
@@ -165,6 +155,13 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
pub enum ControlFlow {
|
||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
||||
/// whether or not new events are available to process.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
|
||||
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
|
||||
/// example when the scaling of the page has changed. This should be treated as an implementation
|
||||
/// detail which should not be relied on.
|
||||
Poll,
|
||||
|
||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
||||
@@ -334,7 +331,6 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
/// Returns the list of all the monitors available on the system.
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
#[allow(clippy::useless_conversion)] // false positive on some platforms
|
||||
self.p
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
@@ -355,20 +351,20 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
.map(|inner| MonitorHandle { inner })
|
||||
}
|
||||
|
||||
/// Change if or when [`DeviceEvent`]s are captured.
|
||||
/// Change [`DeviceEvent`] filter mode.
|
||||
///
|
||||
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
|
||||
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
|
||||
/// this at runtime to explicitly capture them again.
|
||||
/// this filter at runtime to explicitly capture them again.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
|
||||
/// - **Wayland / macOS / iOS / Android / Web / Orbital:** Unsupported.
|
||||
///
|
||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
|
||||
#[cfg(any(x11_platform, wasm_platform, wayland_platform, windows))]
|
||||
self.p.listen_device_events(_allowed);
|
||||
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
|
||||
#[cfg(any(x11_platform, wayland_platform, windows))]
|
||||
self.p.set_device_event_filter(_filter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,14 +422,19 @@ impl<T> fmt::Display for EventLoopClosed<T> {
|
||||
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
|
||||
|
||||
/// Control when device events are captured.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||
pub enum DeviceEvents {
|
||||
/// Report device events regardless of window focus.
|
||||
/// Filter controlling the propagation of device events.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum DeviceEventFilter {
|
||||
/// Always filter out device events.
|
||||
Always,
|
||||
/// Only capture device events while the window is focused.
|
||||
#[default]
|
||||
WhenFocused,
|
||||
/// Never capture device events.
|
||||
/// Filter out device events while the window is not focused.
|
||||
Unfocused,
|
||||
/// Report all device events regardless of window focus.
|
||||
Never,
|
||||
}
|
||||
|
||||
impl Default for DeviceEventFilter {
|
||||
fn default() -> Self {
|
||||
Self::Unfocused
|
||||
}
|
||||
}
|
||||
|
||||
1688
src/keyboard.rs
1688
src/keyboard.rs
File diff suppressed because it is too large
Load Diff
@@ -154,7 +154,6 @@ pub mod error;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
mod icon;
|
||||
pub mod keyboard;
|
||||
pub mod monitor;
|
||||
mod platform_impl;
|
||||
pub mod window;
|
||||
|
||||
@@ -32,9 +32,12 @@ impl PartialOrd for VideoMode {
|
||||
|
||||
impl Ord for VideoMode {
|
||||
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
|
||||
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
|
||||
// to `u32` there
|
||||
let size: (u32, u32) = self.size().into();
|
||||
let other_size: (u32, u32) = other.size().into();
|
||||
self.monitor().cmp(&other.monitor()).then(
|
||||
self.size()
|
||||
.cmp(&other.size())
|
||||
size.cmp(&other_size)
|
||||
.then(
|
||||
self.refresh_rate_millihertz()
|
||||
.cmp(&other.refresh_rate_millihertz())
|
||||
|
||||
@@ -42,11 +42,6 @@ pub trait EventLoopBuilderExtAndroid {
|
||||
///
|
||||
/// This must be called on Android since the `AndroidApp` is not global state.
|
||||
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
|
||||
|
||||
/// Calling this will mark the volume keys to be manually handled by the application
|
||||
///
|
||||
/// Default is to let the operating system handle the volume keys
|
||||
fn handle_volume_keys(&mut self) -> &mut Self;
|
||||
}
|
||||
|
||||
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
|
||||
@@ -54,11 +49,6 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
|
||||
self.platform_specific.android_app = Some(app);
|
||||
self
|
||||
}
|
||||
|
||||
fn handle_volume_keys(&mut self) -> &mut Self {
|
||||
self.platform_specific.ignore_volume_keys = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-export of the `android_activity` API
|
||||
|
||||
@@ -253,10 +253,9 @@ impl MonitorHandleExtIOS for MonitorHandle {
|
||||
}
|
||||
|
||||
/// Valid orientations for a particular [`Window`].
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ValidOrientations {
|
||||
/// Excludes `PortraitUpsideDown` on iphone
|
||||
#[default]
|
||||
LandscapeAndPortrait,
|
||||
|
||||
Landscape,
|
||||
@@ -265,10 +264,17 @@ pub enum ValidOrientations {
|
||||
Portrait,
|
||||
}
|
||||
|
||||
impl Default for ValidOrientations {
|
||||
#[inline]
|
||||
fn default() -> ValidOrientations {
|
||||
ValidOrientations::LandscapeAndPortrait
|
||||
}
|
||||
}
|
||||
|
||||
/// The device [idiom].
|
||||
///
|
||||
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Idiom {
|
||||
Unspecified,
|
||||
|
||||
@@ -287,14 +293,14 @@ bitflags! {
|
||||
/// The [edges] of a screen.
|
||||
///
|
||||
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Default)]
|
||||
pub struct ScreenEdge: u8 {
|
||||
const NONE = 0;
|
||||
const TOP = 1 << 0;
|
||||
const LEFT = 1 << 1;
|
||||
const BOTTOM = 1 << 2;
|
||||
const RIGHT = 1 << 3;
|
||||
const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits()
|
||||
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
|
||||
const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits
|
||||
| ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,19 +122,22 @@ impl WindowExtMacOS for Window {
|
||||
}
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ActivationPolicy {
|
||||
/// Corresponds to `NSApplicationActivationPolicyRegular`.
|
||||
#[default]
|
||||
Regular,
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
|
||||
Accessory,
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
|
||||
Prohibited,
|
||||
}
|
||||
|
||||
impl Default for ActivationPolicy {
|
||||
fn default() -> Self {
|
||||
ActivationPolicy::Regular
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowBuilder`] that are specific to MacOS.
|
||||
///
|
||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
|
||||
@@ -161,7 +164,8 @@ pub trait WindowBuilderExtMacOS {
|
||||
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
||||
/// Window accepts click-through mouse events.
|
||||
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
||||
/// Set how the <kbd>Option</kbd> keys are interpreted.
|
||||
|
||||
/// Set whether the `OptionAsAlt` key is interpreted as the `Alt` modifier.
|
||||
///
|
||||
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
||||
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
|
||||
@@ -344,7 +348,7 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
/// Option as alt behavior.
|
||||
///
|
||||
/// The default is `None`.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum OptionAsAlt {
|
||||
/// The left `Option` key is treated as `Alt`.
|
||||
@@ -357,6 +361,11 @@ pub enum OptionAsAlt {
|
||||
Both,
|
||||
|
||||
/// No special handling is applied for `Option` key.
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for OptionAsAlt {
|
||||
fn default() -> Self {
|
||||
OptionAsAlt::None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ pub mod windows;
|
||||
#[cfg(x11_platform)]
|
||||
pub mod x11;
|
||||
|
||||
pub mod modifier_supplement;
|
||||
#[cfg(any(
|
||||
windows_platform,
|
||||
macos_platform,
|
||||
@@ -42,4 +41,3 @@ pub mod modifier_supplement;
|
||||
orbital_platform
|
||||
))]
|
||||
pub mod run_return;
|
||||
pub mod scancode;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
|
||||
|
||||
use crate::keyboard::Key;
|
||||
|
||||
/// Additional methods for the `KeyEvent` which cannot be implemented on all
|
||||
/// platforms.
|
||||
pub trait KeyEventExtModifierSupplement {
|
||||
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
|
||||
///
|
||||
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
|
||||
fn text_with_all_modifiers(&self) -> Option<&str>;
|
||||
|
||||
/// This value ignores all modifiers including,
|
||||
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
|
||||
/// and <kbd>Ctrl</kbd>. In most cases this means that the
|
||||
/// unicode character in the resulting string is lowercase.
|
||||
///
|
||||
/// This is useful for key-bindings / shortcut key combinations.
|
||||
///
|
||||
/// In case `logical_key` reports `Dead`, this will still report the
|
||||
/// key as `Character` according to the current keyboard layout. This value
|
||||
/// cannot be `Dead`.
|
||||
fn key_without_modifiers(&self) -> Key;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
|
||||
|
||||
use crate::keyboard::KeyCode;
|
||||
|
||||
// TODO: Describe what this value contains for each platform
|
||||
|
||||
/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific
|
||||
/// scancode.
|
||||
///
|
||||
/// [`KeyCode`]: crate::keyboard::KeyCode
|
||||
pub trait KeyCodeExtScancode {
|
||||
/// The raw value of the platform-specific physical key identifier.
|
||||
///
|
||||
/// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Windows:** A 16bit extended scancode
|
||||
/// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8.
|
||||
fn to_scancode(self) -> Option<u32>;
|
||||
|
||||
/// Constructs a `KeyCode` from a platform-specific physical key identifier.
|
||||
///
|
||||
/// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back
|
||||
/// using `to_scancode` might not yield the original value.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract
|
||||
/// `8` to get the value you wanted.
|
||||
fn from_scancode(scancode: u32) -> KeyCode;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::os::raw;
|
||||
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
@@ -41,7 +39,7 @@ impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.p {
|
||||
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
||||
Some(p.connection.display().id().as_ptr() as *mut _)
|
||||
Some(p.display().get_display_ptr() as *mut _)
|
||||
}
|
||||
#[cfg(x11_platform)]
|
||||
_ => None,
|
||||
@@ -96,7 +94,7 @@ impl WindowExtWayland for Window {
|
||||
#[inline]
|
||||
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _),
|
||||
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
|
||||
#[cfg(x11_platform)]
|
||||
_ => None,
|
||||
}
|
||||
@@ -105,7 +103,7 @@ impl WindowExtWayland for Window {
|
||||
#[inline]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _),
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
|
||||
#[cfg(x11_platform)]
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ use crate::window::WindowBuilder;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
pub trait WindowExtWebSys {
|
||||
/// Only returns the canvas if called from inside the window.
|
||||
fn canvas(&self) -> Option<HtmlCanvasElement>;
|
||||
fn canvas(&self) -> HtmlCanvasElement;
|
||||
|
||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
@@ -27,9 +26,6 @@ pub trait WindowBuilderExtWebSys {
|
||||
///
|
||||
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
|
||||
/// the default behavior of scrolling the page.
|
||||
///
|
||||
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
|
||||
/// context menu with Shift+Rightclick.
|
||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
||||
|
||||
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
|
||||
@@ -66,11 +62,6 @@ pub trait EventLoopExtWebSys {
|
||||
///
|
||||
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
|
||||
/// satisfy its `!` return type.
|
||||
///
|
||||
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
|
||||
/// by calling this function again. This can be useful if you want to recreate the event loop
|
||||
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
|
||||
/// event loop when switching between tabs on a single page application.
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static
|
||||
|
||||
@@ -2,11 +2,9 @@ use std::{ffi::c_void, path::Path};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::{DeviceId, KeyEvent},
|
||||
event::DeviceId,
|
||||
event_loop::EventLoopBuilder,
|
||||
keyboard::Key,
|
||||
monitor::MonitorHandle,
|
||||
platform::modifier_supplement::KeyEventExtModifierSupplement,
|
||||
platform_impl::WinIcon,
|
||||
window::{BadIcon, Icon, Window, WindowBuilder},
|
||||
};
|
||||
@@ -346,18 +344,3 @@ impl IconExtWindows for Icon {
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
#[inline]
|
||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
||||
self.platform_specific
|
||||
.text_with_all_modifers
|
||||
.as_ref()
|
||||
.map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn key_without_modifiers(&self) -> Key {
|
||||
self.platform_specific.key_without_modifiers.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,521 +0,0 @@
|
||||
use android_activity::input::Keycode;
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
|
||||
|
||||
pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
|
||||
match keycode {
|
||||
Keycode::A => KeyCode::KeyA,
|
||||
Keycode::B => KeyCode::KeyB,
|
||||
Keycode::C => KeyCode::KeyC,
|
||||
Keycode::D => KeyCode::KeyD,
|
||||
Keycode::E => KeyCode::KeyE,
|
||||
Keycode::F => KeyCode::KeyF,
|
||||
Keycode::G => KeyCode::KeyG,
|
||||
Keycode::H => KeyCode::KeyH,
|
||||
Keycode::I => KeyCode::KeyI,
|
||||
Keycode::J => KeyCode::KeyJ,
|
||||
Keycode::K => KeyCode::KeyK,
|
||||
Keycode::L => KeyCode::KeyL,
|
||||
Keycode::M => KeyCode::KeyM,
|
||||
Keycode::N => KeyCode::KeyN,
|
||||
Keycode::O => KeyCode::KeyO,
|
||||
Keycode::P => KeyCode::KeyP,
|
||||
Keycode::Q => KeyCode::KeyQ,
|
||||
Keycode::R => KeyCode::KeyR,
|
||||
Keycode::S => KeyCode::KeyS,
|
||||
Keycode::T => KeyCode::KeyT,
|
||||
Keycode::U => KeyCode::KeyU,
|
||||
Keycode::V => KeyCode::KeyV,
|
||||
Keycode::W => KeyCode::KeyW,
|
||||
Keycode::X => KeyCode::KeyX,
|
||||
Keycode::Y => KeyCode::KeyY,
|
||||
Keycode::Z => KeyCode::KeyZ,
|
||||
|
||||
Keycode::Keycode0 => KeyCode::Digit0,
|
||||
Keycode::Keycode1 => KeyCode::Digit1,
|
||||
Keycode::Keycode2 => KeyCode::Digit2,
|
||||
Keycode::Keycode3 => KeyCode::Digit3,
|
||||
Keycode::Keycode4 => KeyCode::Digit4,
|
||||
Keycode::Keycode5 => KeyCode::Digit5,
|
||||
Keycode::Keycode6 => KeyCode::Digit6,
|
||||
Keycode::Keycode7 => KeyCode::Digit7,
|
||||
Keycode::Keycode8 => KeyCode::Digit8,
|
||||
Keycode::Keycode9 => KeyCode::Digit9,
|
||||
|
||||
Keycode::Numpad0 => KeyCode::Numpad0,
|
||||
Keycode::Numpad1 => KeyCode::Numpad1,
|
||||
Keycode::Numpad2 => KeyCode::Numpad2,
|
||||
Keycode::Numpad3 => KeyCode::Numpad3,
|
||||
Keycode::Numpad4 => KeyCode::Numpad4,
|
||||
Keycode::Numpad5 => KeyCode::Numpad5,
|
||||
Keycode::Numpad6 => KeyCode::Numpad6,
|
||||
Keycode::Numpad7 => KeyCode::Numpad7,
|
||||
Keycode::Numpad8 => KeyCode::Numpad8,
|
||||
Keycode::Numpad9 => KeyCode::Numpad9,
|
||||
|
||||
Keycode::NumpadAdd => KeyCode::NumpadAdd,
|
||||
Keycode::NumpadSubtract => KeyCode::NumpadSubtract,
|
||||
Keycode::NumpadMultiply => KeyCode::NumpadMultiply,
|
||||
Keycode::NumpadDivide => KeyCode::NumpadDivide,
|
||||
Keycode::NumpadEnter => KeyCode::NumpadEnter,
|
||||
Keycode::NumpadEquals => KeyCode::NumpadEqual,
|
||||
Keycode::NumpadComma => KeyCode::NumpadComma,
|
||||
Keycode::NumpadDot => KeyCode::NumpadDecimal,
|
||||
Keycode::NumLock => KeyCode::NumLock,
|
||||
|
||||
Keycode::DpadLeft => KeyCode::ArrowLeft,
|
||||
Keycode::DpadRight => KeyCode::ArrowRight,
|
||||
Keycode::DpadUp => KeyCode::ArrowUp,
|
||||
Keycode::DpadDown => KeyCode::ArrowDown,
|
||||
|
||||
Keycode::F1 => KeyCode::F1,
|
||||
Keycode::F2 => KeyCode::F2,
|
||||
Keycode::F3 => KeyCode::F3,
|
||||
Keycode::F4 => KeyCode::F4,
|
||||
Keycode::F5 => KeyCode::F5,
|
||||
Keycode::F6 => KeyCode::F6,
|
||||
Keycode::F7 => KeyCode::F7,
|
||||
Keycode::F8 => KeyCode::F8,
|
||||
Keycode::F9 => KeyCode::F9,
|
||||
Keycode::F10 => KeyCode::F10,
|
||||
Keycode::F11 => KeyCode::F11,
|
||||
Keycode::F12 => KeyCode::F12,
|
||||
|
||||
Keycode::Space => KeyCode::Space,
|
||||
Keycode::Escape => KeyCode::Escape,
|
||||
Keycode::Enter => KeyCode::Enter, // not on the Numpad
|
||||
Keycode::Tab => KeyCode::Tab,
|
||||
|
||||
Keycode::PageUp => KeyCode::PageUp,
|
||||
Keycode::PageDown => KeyCode::PageDown,
|
||||
Keycode::MoveHome => KeyCode::Home,
|
||||
Keycode::MoveEnd => KeyCode::End,
|
||||
Keycode::Insert => KeyCode::Insert,
|
||||
|
||||
Keycode::Del => KeyCode::Backspace, // Backspace (above Enter)
|
||||
Keycode::ForwardDel => KeyCode::Delete, // Delete (below Insert)
|
||||
|
||||
Keycode::Copy => KeyCode::Copy,
|
||||
Keycode::Paste => KeyCode::Paste,
|
||||
Keycode::Cut => KeyCode::Cut,
|
||||
|
||||
Keycode::VolumeUp => KeyCode::AudioVolumeUp,
|
||||
Keycode::VolumeDown => KeyCode::AudioVolumeDown,
|
||||
Keycode::VolumeMute => KeyCode::AudioVolumeMute,
|
||||
//Keycode::Mute => None, // Microphone mute
|
||||
Keycode::MediaPlayPause => KeyCode::MediaPlayPause,
|
||||
Keycode::MediaStop => KeyCode::MediaStop,
|
||||
Keycode::MediaNext => KeyCode::MediaTrackNext,
|
||||
Keycode::MediaPrevious => KeyCode::MediaTrackPrevious,
|
||||
|
||||
Keycode::Plus => KeyCode::Equal,
|
||||
Keycode::Minus => KeyCode::Minus,
|
||||
// Winit doesn't differentiate both '+' and '=', considering they are usually
|
||||
// on the same physical key
|
||||
Keycode::Equals => KeyCode::Equal,
|
||||
Keycode::Semicolon => KeyCode::Semicolon,
|
||||
Keycode::Slash => KeyCode::Slash,
|
||||
Keycode::Backslash => KeyCode::Backslash,
|
||||
Keycode::Comma => KeyCode::Comma,
|
||||
Keycode::Period => KeyCode::Period,
|
||||
Keycode::Apostrophe => KeyCode::Quote,
|
||||
Keycode::Grave => KeyCode::Backquote,
|
||||
|
||||
// Winit doesn't expose a SysRq code, so map to PrintScreen since it's
|
||||
// usually the same physical key
|
||||
Keycode::Sysrq => KeyCode::PrintScreen,
|
||||
// These are usually the same (Pause/Break)
|
||||
Keycode::Break => KeyCode::Pause,
|
||||
// These are exactly the same
|
||||
Keycode::ScrollLock => KeyCode::ScrollLock,
|
||||
|
||||
Keycode::Yen => KeyCode::IntlYen,
|
||||
Keycode::Kana => KeyCode::Lang1,
|
||||
Keycode::KatakanaHiragana => KeyCode::KanaMode,
|
||||
|
||||
Keycode::CtrlLeft => KeyCode::ControlLeft,
|
||||
Keycode::CtrlRight => KeyCode::ControlRight,
|
||||
|
||||
Keycode::ShiftLeft => KeyCode::ShiftLeft,
|
||||
Keycode::ShiftRight => KeyCode::ShiftRight,
|
||||
|
||||
Keycode::AltLeft => KeyCode::AltLeft,
|
||||
Keycode::AltRight => KeyCode::AltRight,
|
||||
|
||||
Keycode::MetaLeft => KeyCode::SuperLeft,
|
||||
Keycode::MetaRight => KeyCode::SuperRight,
|
||||
|
||||
Keycode::LeftBracket => KeyCode::BracketLeft,
|
||||
Keycode::RightBracket => KeyCode::BracketRight,
|
||||
|
||||
Keycode::Power => KeyCode::Power,
|
||||
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
|
||||
Keycode::Wakeup => KeyCode::WakeUp,
|
||||
|
||||
keycode => KeyCode::Unidentified(NativeKeyCode::Android(keycode.into())),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We need to expose getUnicodeChar via android-activity instead of having
|
||||
// a fixed mapping from key codes
|
||||
pub fn to_logical(keycode: Keycode, native: NativeKey) -> Key {
|
||||
use android_activity::input::Keycode::*;
|
||||
|
||||
match keycode {
|
||||
Unknown => Key::Unidentified(native),
|
||||
|
||||
// Can be added on demand
|
||||
SoftLeft => Key::Unidentified(native),
|
||||
SoftRight => Key::Unidentified(native),
|
||||
|
||||
// Using `BrowserHome` instead of `GoHome` according to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
Home => Key::BrowserHome,
|
||||
Back => Key::BrowserBack,
|
||||
Call => Key::Call,
|
||||
Endcall => Key::EndCall,
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// Reporting unidentified, because the specific character is layout dependent.
|
||||
// (I'm not sure though)
|
||||
Keycode0 => Key::Character("0".into()),
|
||||
Keycode1 => Key::Character("1".into()),
|
||||
Keycode2 => Key::Character("2".into()),
|
||||
Keycode3 => Key::Character("3".into()),
|
||||
Keycode4 => Key::Character("4".into()),
|
||||
Keycode5 => Key::Character("5".into()),
|
||||
Keycode6 => Key::Character("6".into()),
|
||||
Keycode7 => Key::Character("7".into()),
|
||||
Keycode8 => Key::Character("8".into()),
|
||||
Keycode9 => Key::Character("9".into()),
|
||||
Star => Key::Character("*".into()),
|
||||
Pound => Key::Character("#".into()),
|
||||
A => Key::Character("a".into()),
|
||||
B => Key::Character("b".into()),
|
||||
C => Key::Character("c".into()),
|
||||
D => Key::Character("d".into()),
|
||||
E => Key::Character("e".into()),
|
||||
F => Key::Character("f".into()),
|
||||
G => Key::Character("g".into()),
|
||||
H => Key::Character("h".into()),
|
||||
I => Key::Character("i".into()),
|
||||
J => Key::Character("j".into()),
|
||||
K => Key::Character("k".into()),
|
||||
L => Key::Character("l".into()),
|
||||
M => Key::Character("m".into()),
|
||||
N => Key::Character("n".into()),
|
||||
O => Key::Character("o".into()),
|
||||
P => Key::Character("p".into()),
|
||||
Q => Key::Character("q".into()),
|
||||
R => Key::Character("r".into()),
|
||||
S => Key::Character("s".into()),
|
||||
T => Key::Character("t".into()),
|
||||
U => Key::Character("u".into()),
|
||||
V => Key::Character("v".into()),
|
||||
W => Key::Character("w".into()),
|
||||
X => Key::Character("x".into()),
|
||||
Y => Key::Character("y".into()),
|
||||
Z => Key::Character("z".into()),
|
||||
Comma => Key::Character(",".into()),
|
||||
Period => Key::Character(".".into()),
|
||||
Grave => Key::Character("`".into()),
|
||||
Minus => Key::Character("-".into()),
|
||||
Equals => Key::Character("=".into()),
|
||||
LeftBracket => Key::Character("[".into()),
|
||||
RightBracket => Key::Character("]".into()),
|
||||
Backslash => Key::Character("\\".into()),
|
||||
Semicolon => Key::Character(";".into()),
|
||||
Apostrophe => Key::Character("'".into()),
|
||||
Slash => Key::Character("/".into()),
|
||||
At => Key::Character("@".into()),
|
||||
Plus => Key::Character("+".into()),
|
||||
//-------------------------------------------------------------------------------
|
||||
DpadUp => Key::ArrowUp,
|
||||
DpadDown => Key::ArrowDown,
|
||||
DpadLeft => Key::ArrowLeft,
|
||||
DpadRight => Key::ArrowRight,
|
||||
DpadCenter => Key::Enter,
|
||||
|
||||
VolumeUp => Key::AudioVolumeUp,
|
||||
VolumeDown => Key::AudioVolumeDown,
|
||||
Power => Key::Power,
|
||||
Camera => Key::Camera,
|
||||
Clear => Key::Clear,
|
||||
|
||||
AltLeft => Key::Alt,
|
||||
AltRight => Key::Alt,
|
||||
ShiftLeft => Key::Shift,
|
||||
ShiftRight => Key::Shift,
|
||||
Tab => Key::Tab,
|
||||
Space => Key::Space,
|
||||
Sym => Key::Symbol,
|
||||
Explorer => Key::LaunchWebBrowser,
|
||||
Envelope => Key::LaunchMail,
|
||||
Enter => Key::Enter,
|
||||
Del => Key::Backspace,
|
||||
|
||||
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
|
||||
Num => Key::Alt,
|
||||
|
||||
Headsethook => Key::HeadsetHook,
|
||||
Focus => Key::CameraFocus,
|
||||
|
||||
Menu => Key::Unidentified(native),
|
||||
|
||||
Notification => Key::Notification,
|
||||
Search => Key::BrowserSearch,
|
||||
MediaPlayPause => Key::MediaPlayPause,
|
||||
MediaStop => Key::MediaStop,
|
||||
MediaNext => Key::MediaTrackNext,
|
||||
MediaPrevious => Key::MediaTrackPrevious,
|
||||
MediaRewind => Key::MediaRewind,
|
||||
MediaFastForward => Key::MediaFastForward,
|
||||
Mute => Key::MicrophoneVolumeMute,
|
||||
PageUp => Key::PageUp,
|
||||
PageDown => Key::PageDown,
|
||||
Pictsymbols => Key::Unidentified(native),
|
||||
SwitchCharset => Key::Unidentified(native),
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Gamepad events should be exposed through a separate API, not
|
||||
// keyboard events
|
||||
ButtonA => Key::Unidentified(native),
|
||||
ButtonB => Key::Unidentified(native),
|
||||
ButtonC => Key::Unidentified(native),
|
||||
ButtonX => Key::Unidentified(native),
|
||||
ButtonY => Key::Unidentified(native),
|
||||
ButtonZ => Key::Unidentified(native),
|
||||
ButtonL1 => Key::Unidentified(native),
|
||||
ButtonR1 => Key::Unidentified(native),
|
||||
ButtonL2 => Key::Unidentified(native),
|
||||
ButtonR2 => Key::Unidentified(native),
|
||||
ButtonThumbl => Key::Unidentified(native),
|
||||
ButtonThumbr => Key::Unidentified(native),
|
||||
ButtonStart => Key::Unidentified(native),
|
||||
ButtonSelect => Key::Unidentified(native),
|
||||
ButtonMode => Key::Unidentified(native),
|
||||
// -----------------------------------------------------------------
|
||||
Escape => Key::Escape,
|
||||
ForwardDel => Key::Delete,
|
||||
CtrlLeft => Key::Control,
|
||||
CtrlRight => Key::Control,
|
||||
CapsLock => Key::CapsLock,
|
||||
ScrollLock => Key::ScrollLock,
|
||||
MetaLeft => Key::Super,
|
||||
MetaRight => Key::Super,
|
||||
Function => Key::Fn,
|
||||
Sysrq => Key::PrintScreen,
|
||||
Break => Key::Pause,
|
||||
MoveHome => Key::Home,
|
||||
MoveEnd => Key::End,
|
||||
Insert => Key::Insert,
|
||||
Forward => Key::BrowserForward,
|
||||
MediaPlay => Key::MediaPlay,
|
||||
MediaPause => Key::MediaPause,
|
||||
MediaClose => Key::MediaClose,
|
||||
MediaEject => Key::Eject,
|
||||
MediaRecord => Key::MediaRecord,
|
||||
F1 => Key::F1,
|
||||
F2 => Key::F2,
|
||||
F3 => Key::F3,
|
||||
F4 => Key::F4,
|
||||
F5 => Key::F5,
|
||||
F6 => Key::F6,
|
||||
F7 => Key::F7,
|
||||
F8 => Key::F8,
|
||||
F9 => Key::F9,
|
||||
F10 => Key::F10,
|
||||
F11 => Key::F11,
|
||||
F12 => Key::F12,
|
||||
NumLock => Key::NumLock,
|
||||
Numpad0 => Key::Character("0".into()),
|
||||
Numpad1 => Key::Character("1".into()),
|
||||
Numpad2 => Key::Character("2".into()),
|
||||
Numpad3 => Key::Character("3".into()),
|
||||
Numpad4 => Key::Character("4".into()),
|
||||
Numpad5 => Key::Character("5".into()),
|
||||
Numpad6 => Key::Character("6".into()),
|
||||
Numpad7 => Key::Character("7".into()),
|
||||
Numpad8 => Key::Character("8".into()),
|
||||
Numpad9 => Key::Character("9".into()),
|
||||
NumpadDivide => Key::Character("/".into()),
|
||||
NumpadMultiply => Key::Character("*".into()),
|
||||
NumpadSubtract => Key::Character("-".into()),
|
||||
NumpadAdd => Key::Character("+".into()),
|
||||
NumpadDot => Key::Character(".".into()),
|
||||
NumpadComma => Key::Character(",".into()),
|
||||
NumpadEnter => Key::Enter,
|
||||
NumpadEquals => Key::Character("=".into()),
|
||||
NumpadLeftParen => Key::Character("(".into()),
|
||||
NumpadRightParen => Key::Character(")".into()),
|
||||
|
||||
VolumeMute => Key::AudioVolumeMute,
|
||||
Info => Key::Info,
|
||||
ChannelUp => Key::ChannelUp,
|
||||
ChannelDown => Key::ChannelDown,
|
||||
ZoomIn => Key::ZoomIn,
|
||||
ZoomOut => Key::ZoomOut,
|
||||
Tv => Key::TV,
|
||||
Window => Key::Unidentified(native),
|
||||
Guide => Key::Guide,
|
||||
Dvr => Key::DVR,
|
||||
Bookmark => Key::BrowserFavorites,
|
||||
Captions => Key::ClosedCaptionToggle,
|
||||
Settings => Key::Settings,
|
||||
TvPower => Key::TVPower,
|
||||
TvInput => Key::TVInput,
|
||||
StbPower => Key::STBPower,
|
||||
StbInput => Key::STBInput,
|
||||
AvrPower => Key::AVRPower,
|
||||
AvrInput => Key::AVRInput,
|
||||
ProgRed => Key::ColorF0Red,
|
||||
ProgGreen => Key::ColorF1Green,
|
||||
ProgYellow => Key::ColorF2Yellow,
|
||||
ProgBlue => Key::ColorF3Blue,
|
||||
AppSwitch => Key::AppSwitch,
|
||||
Button1 => Key::Unidentified(native),
|
||||
Button2 => Key::Unidentified(native),
|
||||
Button3 => Key::Unidentified(native),
|
||||
Button4 => Key::Unidentified(native),
|
||||
Button5 => Key::Unidentified(native),
|
||||
Button6 => Key::Unidentified(native),
|
||||
Button7 => Key::Unidentified(native),
|
||||
Button8 => Key::Unidentified(native),
|
||||
Button9 => Key::Unidentified(native),
|
||||
Button10 => Key::Unidentified(native),
|
||||
Button11 => Key::Unidentified(native),
|
||||
Button12 => Key::Unidentified(native),
|
||||
Button13 => Key::Unidentified(native),
|
||||
Button14 => Key::Unidentified(native),
|
||||
Button15 => Key::Unidentified(native),
|
||||
Button16 => Key::Unidentified(native),
|
||||
LanguageSwitch => Key::GroupNext,
|
||||
MannerMode => Key::MannerMode,
|
||||
Keycode3dMode => Key::TV3DMode,
|
||||
Contacts => Key::LaunchContacts,
|
||||
Calendar => Key::LaunchCalendar,
|
||||
Music => Key::LaunchMusicPlayer,
|
||||
Calculator => Key::LaunchApplication2,
|
||||
ZenkakuHankaku => Key::ZenkakuHankaku,
|
||||
Eisu => Key::Eisu,
|
||||
Muhenkan => Key::NonConvert,
|
||||
Henkan => Key::Convert,
|
||||
KatakanaHiragana => Key::HiraganaKatakana,
|
||||
Yen => Key::Unidentified(native),
|
||||
Ro => Key::Unidentified(native),
|
||||
Kana => Key::KanjiMode,
|
||||
Assist => Key::Unidentified(native),
|
||||
BrightnessDown => Key::BrightnessDown,
|
||||
BrightnessUp => Key::BrightnessUp,
|
||||
MediaAudioTrack => Key::MediaAudioTrack,
|
||||
Sleep => Key::Standby,
|
||||
Wakeup => Key::WakeUp,
|
||||
Pairing => Key::Pairing,
|
||||
MediaTopMenu => Key::MediaTopMenu,
|
||||
Keycode11 => Key::Unidentified(native),
|
||||
Keycode12 => Key::Unidentified(native),
|
||||
LastChannel => Key::MediaLast,
|
||||
TvDataService => Key::TVDataService,
|
||||
VoiceAssist => Key::VoiceDial,
|
||||
TvRadioService => Key::TVRadioService,
|
||||
TvTeletext => Key::Teletext,
|
||||
TvNumberEntry => Key::TVNumberEntry,
|
||||
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
|
||||
TvTerrestrialDigital => Key::TVTerrestrialDigital,
|
||||
TvSatellite => Key::TVSatellite,
|
||||
TvSatelliteBs => Key::TVSatelliteBS,
|
||||
TvSatelliteCs => Key::TVSatelliteCS,
|
||||
TvSatelliteService => Key::TVSatelliteToggle,
|
||||
TvNetwork => Key::TVNetwork,
|
||||
TvAntennaCable => Key::TVAntennaCable,
|
||||
TvInputHdmi1 => Key::TVInputHDMI1,
|
||||
TvInputHdmi2 => Key::TVInputHDMI2,
|
||||
TvInputHdmi3 => Key::TVInputHDMI3,
|
||||
TvInputHdmi4 => Key::TVInputHDMI4,
|
||||
TvInputComposite1 => Key::TVInputComposite1,
|
||||
TvInputComposite2 => Key::TVInputComposite2,
|
||||
TvInputComponent1 => Key::TVInputComponent1,
|
||||
TvInputComponent2 => Key::TVInputComponent2,
|
||||
TvInputVga1 => Key::TVInputVGA1,
|
||||
TvAudioDescription => Key::TVAudioDescription,
|
||||
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
|
||||
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
|
||||
TvZoomMode => Key::ZoomToggle,
|
||||
TvContentsMenu => Key::TVContentsMenu,
|
||||
TvMediaContextMenu => Key::TVMediaContext,
|
||||
TvTimerProgramming => Key::TVTimer,
|
||||
Help => Key::Help,
|
||||
NavigatePrevious => Key::NavigatePrevious,
|
||||
NavigateNext => Key::NavigateNext,
|
||||
NavigateIn => Key::NavigateIn,
|
||||
NavigateOut => Key::NavigateOut,
|
||||
StemPrimary => Key::Unidentified(native),
|
||||
Stem1 => Key::Unidentified(native),
|
||||
Stem2 => Key::Unidentified(native),
|
||||
Stem3 => Key::Unidentified(native),
|
||||
DpadUpLeft => Key::Unidentified(native),
|
||||
DpadDownLeft => Key::Unidentified(native),
|
||||
DpadUpRight => Key::Unidentified(native),
|
||||
DpadDownRight => Key::Unidentified(native),
|
||||
MediaSkipForward => Key::MediaSkipForward,
|
||||
MediaSkipBackward => Key::MediaSkipBackward,
|
||||
MediaStepForward => Key::MediaStepForward,
|
||||
MediaStepBackward => Key::MediaStepBackward,
|
||||
SoftSleep => Key::Unidentified(native),
|
||||
Cut => Key::Cut,
|
||||
Copy => Key::Copy,
|
||||
Paste => Key::Paste,
|
||||
SystemNavigationUp => Key::Unidentified(native),
|
||||
SystemNavigationDown => Key::Unidentified(native),
|
||||
SystemNavigationLeft => Key::Unidentified(native),
|
||||
SystemNavigationRight => Key::Unidentified(native),
|
||||
AllApps => Key::Unidentified(native),
|
||||
Refresh => Key::BrowserRefresh,
|
||||
ThumbsUp => Key::Unidentified(native),
|
||||
ThumbsDown => Key::Unidentified(native),
|
||||
ProfileSwitch => Key::Unidentified(native),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_location(keycode: Keycode) -> KeyLocation {
|
||||
use android_activity::input::Keycode::*;
|
||||
|
||||
match keycode {
|
||||
AltLeft => KeyLocation::Left,
|
||||
AltRight => KeyLocation::Right,
|
||||
ShiftLeft => KeyLocation::Left,
|
||||
ShiftRight => KeyLocation::Right,
|
||||
|
||||
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
|
||||
Num => KeyLocation::Left,
|
||||
|
||||
CtrlLeft => KeyLocation::Left,
|
||||
CtrlRight => KeyLocation::Right,
|
||||
MetaLeft => KeyLocation::Left,
|
||||
MetaRight => KeyLocation::Right,
|
||||
|
||||
NumLock => KeyLocation::Numpad,
|
||||
Numpad0 => KeyLocation::Numpad,
|
||||
Numpad1 => KeyLocation::Numpad,
|
||||
Numpad2 => KeyLocation::Numpad,
|
||||
Numpad3 => KeyLocation::Numpad,
|
||||
Numpad4 => KeyLocation::Numpad,
|
||||
Numpad5 => KeyLocation::Numpad,
|
||||
Numpad6 => KeyLocation::Numpad,
|
||||
Numpad7 => KeyLocation::Numpad,
|
||||
Numpad8 => KeyLocation::Numpad,
|
||||
Numpad9 => KeyLocation::Numpad,
|
||||
NumpadDivide => KeyLocation::Numpad,
|
||||
NumpadMultiply => KeyLocation::Numpad,
|
||||
NumpadSubtract => KeyLocation::Numpad,
|
||||
NumpadAdd => KeyLocation::Numpad,
|
||||
NumpadDot => KeyLocation::Numpad,
|
||||
NumpadComma => KeyLocation::Numpad,
|
||||
NumpadEnter => KeyLocation::Numpad,
|
||||
NumpadEquals => KeyLocation::Numpad,
|
||||
NumpadLeftParen => KeyLocation::Numpad,
|
||||
NumpadRightParen => KeyLocation::Numpad,
|
||||
|
||||
_ => KeyLocation::Standard,
|
||||
}
|
||||
}
|
||||
@@ -23,18 +23,179 @@ use crate::platform_impl::Fullscreen;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error,
|
||||
event::{self, StartCause},
|
||||
event::{self, StartCause, VirtualKeyCode},
|
||||
event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW},
|
||||
keyboard::NativeKey,
|
||||
window::{
|
||||
self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel,
|
||||
},
|
||||
};
|
||||
|
||||
mod keycodes;
|
||||
|
||||
static HAS_FOCUS: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
|
||||
|
||||
fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option<event::VirtualKeyCode> {
|
||||
match keycode {
|
||||
Keycode::A => Some(VirtualKeyCode::A),
|
||||
Keycode::B => Some(VirtualKeyCode::B),
|
||||
Keycode::C => Some(VirtualKeyCode::C),
|
||||
Keycode::D => Some(VirtualKeyCode::D),
|
||||
Keycode::E => Some(VirtualKeyCode::E),
|
||||
Keycode::F => Some(VirtualKeyCode::F),
|
||||
Keycode::G => Some(VirtualKeyCode::G),
|
||||
Keycode::H => Some(VirtualKeyCode::H),
|
||||
Keycode::I => Some(VirtualKeyCode::I),
|
||||
Keycode::J => Some(VirtualKeyCode::J),
|
||||
Keycode::K => Some(VirtualKeyCode::K),
|
||||
Keycode::L => Some(VirtualKeyCode::L),
|
||||
Keycode::M => Some(VirtualKeyCode::M),
|
||||
Keycode::N => Some(VirtualKeyCode::N),
|
||||
Keycode::O => Some(VirtualKeyCode::O),
|
||||
Keycode::P => Some(VirtualKeyCode::P),
|
||||
Keycode::Q => Some(VirtualKeyCode::Q),
|
||||
Keycode::R => Some(VirtualKeyCode::R),
|
||||
Keycode::S => Some(VirtualKeyCode::S),
|
||||
Keycode::T => Some(VirtualKeyCode::T),
|
||||
Keycode::U => Some(VirtualKeyCode::U),
|
||||
Keycode::V => Some(VirtualKeyCode::V),
|
||||
Keycode::W => Some(VirtualKeyCode::W),
|
||||
Keycode::X => Some(VirtualKeyCode::X),
|
||||
Keycode::Y => Some(VirtualKeyCode::Y),
|
||||
Keycode::Z => Some(VirtualKeyCode::Z),
|
||||
|
||||
Keycode::Keycode0 => Some(VirtualKeyCode::Key0),
|
||||
Keycode::Keycode1 => Some(VirtualKeyCode::Key1),
|
||||
Keycode::Keycode2 => Some(VirtualKeyCode::Key2),
|
||||
Keycode::Keycode3 => Some(VirtualKeyCode::Key3),
|
||||
Keycode::Keycode4 => Some(VirtualKeyCode::Key4),
|
||||
Keycode::Keycode5 => Some(VirtualKeyCode::Key5),
|
||||
Keycode::Keycode6 => Some(VirtualKeyCode::Key6),
|
||||
Keycode::Keycode7 => Some(VirtualKeyCode::Key7),
|
||||
Keycode::Keycode8 => Some(VirtualKeyCode::Key8),
|
||||
Keycode::Keycode9 => Some(VirtualKeyCode::Key9),
|
||||
|
||||
Keycode::Numpad0 => Some(VirtualKeyCode::Numpad0),
|
||||
Keycode::Numpad1 => Some(VirtualKeyCode::Numpad1),
|
||||
Keycode::Numpad2 => Some(VirtualKeyCode::Numpad2),
|
||||
Keycode::Numpad3 => Some(VirtualKeyCode::Numpad3),
|
||||
Keycode::Numpad4 => Some(VirtualKeyCode::Numpad4),
|
||||
Keycode::Numpad5 => Some(VirtualKeyCode::Numpad5),
|
||||
Keycode::Numpad6 => Some(VirtualKeyCode::Numpad6),
|
||||
Keycode::Numpad7 => Some(VirtualKeyCode::Numpad7),
|
||||
Keycode::Numpad8 => Some(VirtualKeyCode::Numpad8),
|
||||
Keycode::Numpad9 => Some(VirtualKeyCode::Numpad9),
|
||||
|
||||
Keycode::NumpadAdd => Some(VirtualKeyCode::NumpadAdd),
|
||||
Keycode::NumpadSubtract => Some(VirtualKeyCode::NumpadSubtract),
|
||||
Keycode::NumpadMultiply => Some(VirtualKeyCode::NumpadMultiply),
|
||||
Keycode::NumpadDivide => Some(VirtualKeyCode::NumpadDivide),
|
||||
Keycode::NumpadEnter => Some(VirtualKeyCode::NumpadEnter),
|
||||
Keycode::NumpadEquals => Some(VirtualKeyCode::NumpadEquals),
|
||||
Keycode::NumpadComma => Some(VirtualKeyCode::NumpadComma),
|
||||
Keycode::NumpadDot => Some(VirtualKeyCode::NumpadDecimal),
|
||||
Keycode::NumLock => Some(VirtualKeyCode::Numlock),
|
||||
|
||||
Keycode::DpadLeft => Some(VirtualKeyCode::Left),
|
||||
Keycode::DpadRight => Some(VirtualKeyCode::Right),
|
||||
Keycode::DpadUp => Some(VirtualKeyCode::Up),
|
||||
Keycode::DpadDown => Some(VirtualKeyCode::Down),
|
||||
|
||||
Keycode::F1 => Some(VirtualKeyCode::F1),
|
||||
Keycode::F2 => Some(VirtualKeyCode::F2),
|
||||
Keycode::F3 => Some(VirtualKeyCode::F3),
|
||||
Keycode::F4 => Some(VirtualKeyCode::F4),
|
||||
Keycode::F5 => Some(VirtualKeyCode::F5),
|
||||
Keycode::F6 => Some(VirtualKeyCode::F6),
|
||||
Keycode::F7 => Some(VirtualKeyCode::F7),
|
||||
Keycode::F8 => Some(VirtualKeyCode::F8),
|
||||
Keycode::F9 => Some(VirtualKeyCode::F9),
|
||||
Keycode::F10 => Some(VirtualKeyCode::F10),
|
||||
Keycode::F11 => Some(VirtualKeyCode::F11),
|
||||
Keycode::F12 => Some(VirtualKeyCode::F12),
|
||||
|
||||
Keycode::Space => Some(VirtualKeyCode::Space),
|
||||
Keycode::Escape => Some(VirtualKeyCode::Escape),
|
||||
Keycode::Enter => Some(VirtualKeyCode::Return), // not on the Numpad
|
||||
Keycode::Tab => Some(VirtualKeyCode::Tab),
|
||||
|
||||
Keycode::PageUp => Some(VirtualKeyCode::PageUp),
|
||||
Keycode::PageDown => Some(VirtualKeyCode::PageDown),
|
||||
Keycode::MoveHome => Some(VirtualKeyCode::Home),
|
||||
Keycode::MoveEnd => Some(VirtualKeyCode::End),
|
||||
Keycode::Insert => Some(VirtualKeyCode::Insert),
|
||||
|
||||
Keycode::Del => Some(VirtualKeyCode::Back), // Backspace (above Enter)
|
||||
Keycode::ForwardDel => Some(VirtualKeyCode::Delete), // Delete (below Insert)
|
||||
|
||||
Keycode::Copy => Some(VirtualKeyCode::Copy),
|
||||
Keycode::Paste => Some(VirtualKeyCode::Paste),
|
||||
Keycode::Cut => Some(VirtualKeyCode::Cut),
|
||||
|
||||
Keycode::VolumeUp => Some(VirtualKeyCode::VolumeUp),
|
||||
Keycode::VolumeDown => Some(VirtualKeyCode::VolumeDown),
|
||||
Keycode::VolumeMute => Some(VirtualKeyCode::Mute), // ???
|
||||
Keycode::Mute => Some(VirtualKeyCode::Mute), // ???
|
||||
Keycode::MediaPlayPause => Some(VirtualKeyCode::PlayPause),
|
||||
Keycode::MediaStop => Some(VirtualKeyCode::MediaStop), // ??? simple "Stop"?
|
||||
Keycode::MediaNext => Some(VirtualKeyCode::NextTrack),
|
||||
Keycode::MediaPrevious => Some(VirtualKeyCode::PrevTrack),
|
||||
|
||||
Keycode::Plus => Some(VirtualKeyCode::Plus),
|
||||
Keycode::Minus => Some(VirtualKeyCode::Minus),
|
||||
Keycode::Equals => Some(VirtualKeyCode::Equals),
|
||||
Keycode::Semicolon => Some(VirtualKeyCode::Semicolon),
|
||||
Keycode::Slash => Some(VirtualKeyCode::Slash),
|
||||
Keycode::Backslash => Some(VirtualKeyCode::Backslash),
|
||||
Keycode::Comma => Some(VirtualKeyCode::Comma),
|
||||
Keycode::Period => Some(VirtualKeyCode::Period),
|
||||
Keycode::Apostrophe => Some(VirtualKeyCode::Apostrophe),
|
||||
Keycode::Grave => Some(VirtualKeyCode::Grave),
|
||||
Keycode::At => Some(VirtualKeyCode::At),
|
||||
|
||||
// TODO: Maybe mapping this to Snapshot makes more sense? See: "PrtScr/SysRq"
|
||||
Keycode::Sysrq => Some(VirtualKeyCode::Sysrq),
|
||||
// These are usually the same (Pause/Break)
|
||||
Keycode::Break => Some(VirtualKeyCode::Pause),
|
||||
// These are exactly the same
|
||||
Keycode::ScrollLock => Some(VirtualKeyCode::Scroll),
|
||||
|
||||
Keycode::Yen => Some(VirtualKeyCode::Yen),
|
||||
Keycode::Kana => Some(VirtualKeyCode::Kana),
|
||||
|
||||
Keycode::CtrlLeft => Some(VirtualKeyCode::LControl),
|
||||
Keycode::CtrlRight => Some(VirtualKeyCode::RControl),
|
||||
|
||||
Keycode::ShiftLeft => Some(VirtualKeyCode::LShift),
|
||||
Keycode::ShiftRight => Some(VirtualKeyCode::RShift),
|
||||
|
||||
Keycode::AltLeft => Some(VirtualKeyCode::LAlt),
|
||||
Keycode::AltRight => Some(VirtualKeyCode::RAlt),
|
||||
|
||||
// Different names for the same keys
|
||||
Keycode::MetaLeft => Some(VirtualKeyCode::LWin),
|
||||
Keycode::MetaRight => Some(VirtualKeyCode::RWin),
|
||||
|
||||
Keycode::LeftBracket => Some(VirtualKeyCode::LBracket),
|
||||
Keycode::RightBracket => Some(VirtualKeyCode::RBracket),
|
||||
|
||||
Keycode::Power => Some(VirtualKeyCode::Power),
|
||||
Keycode::Sleep => Some(VirtualKeyCode::Sleep), // what about SoftSleep?
|
||||
Keycode::Wakeup => Some(VirtualKeyCode::Wake),
|
||||
|
||||
Keycode::NavigateNext => Some(VirtualKeyCode::NavigateForward),
|
||||
Keycode::NavigatePrevious => Some(VirtualKeyCode::NavigateBackward),
|
||||
|
||||
Keycode::Calculator => Some(VirtualKeyCode::Calculator),
|
||||
Keycode::Explorer => Some(VirtualKeyCode::MyComputer), // "close enough"
|
||||
Keycode::Envelope => Some(VirtualKeyCode::Mail), // "close enough"
|
||||
|
||||
Keycode::Star => Some(VirtualKeyCode::Asterisk), // ???
|
||||
Keycode::AllApps => Some(VirtualKeyCode::Apps), // ???
|
||||
Keycode::AppSwitch => Some(VirtualKeyCode::Apps), // ???
|
||||
Keycode::Refresh => Some(VirtualKeyCode::WebRefresh), // ???
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
struct PeekableReceiver<T> {
|
||||
recv: mpsc::Receiver<T>,
|
||||
first: Option<T>,
|
||||
@@ -126,9 +287,6 @@ impl RedrawRequester {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
android_app: AndroidApp,
|
||||
window_target: event_loop::EventLoopWindowTarget<T>,
|
||||
@@ -136,22 +294,11 @@ pub struct EventLoop<T: 'static> {
|
||||
user_events_sender: mpsc::Sender<T>,
|
||||
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent
|
||||
running: bool,
|
||||
ignore_volume_keys: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
||||
pub(crate) android_app: Option<AndroidApp>,
|
||||
pub(crate) ignore_volume_keys: bool,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificEventLoopAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
android_app: Default::default(),
|
||||
ignore_volume_keys: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sticky_exit_callback<T, F>(
|
||||
@@ -201,7 +348,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
user_events_sender,
|
||||
user_events_receiver: PeekableReceiver::from_recv(user_events_receiver),
|
||||
running: false,
|
||||
ignore_volume_keys: attributes.ignore_volume_keys,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,8 +483,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
// Process input events
|
||||
|
||||
self.android_app.input_events(|event| {
|
||||
let mut input_status = InputStatus::Handled;
|
||||
match event {
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
let window_id = window::WindowId(WindowId);
|
||||
@@ -405,58 +551,44 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
InputEvent::KeyEvent(key) => {
|
||||
match key.key_code() {
|
||||
// Flag keys related to volume as unhandled. While winit does not have a way for applications
|
||||
// to configure what keys to flag as handled, this appears to be a good default until winit
|
||||
// can be configured.
|
||||
Keycode::VolumeUp |
|
||||
Keycode::VolumeDown |
|
||||
Keycode::VolumeMute => {
|
||||
if self.ignore_volume_keys {
|
||||
input_status = InputStatus::Unhandled
|
||||
}
|
||||
let device_id = event::DeviceId(DeviceId);
|
||||
|
||||
let state = match key.action() {
|
||||
KeyAction::Down => event::ElementState::Pressed,
|
||||
KeyAction::Up => event::ElementState::Released,
|
||||
_ => event::ElementState::Released,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
input: event::KeyboardInput {
|
||||
scancode: key.scan_code() as u32,
|
||||
state,
|
||||
virtual_keycode: ndk_keycode_to_virtualkeycode(
|
||||
key.key_code(),
|
||||
),
|
||||
modifiers: event::ModifiersState::default(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
keycode => {
|
||||
let state = match key.action() {
|
||||
KeyAction::Down => event::ElementState::Pressed,
|
||||
KeyAction::Up => event::ElementState::Released,
|
||||
_ => event::ElementState::Released,
|
||||
};
|
||||
|
||||
let native = NativeKey::Android(keycode.into());
|
||||
let logical_key = keycodes::to_logical(keycode, native);
|
||||
// TODO: maybe use getUnicodeChar to get the logical key
|
||||
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id: event::DeviceId(DeviceId),
|
||||
event: event::KeyEvent {
|
||||
state,
|
||||
physical_key: keycodes::to_physical_keycode(keycode),
|
||||
logical_key,
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
text: None,
|
||||
platform_specific: KeyEventExtra {},
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
};
|
||||
sticky_exit_callback(
|
||||
event,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
sticky_exit_callback(
|
||||
event,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
callback
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
warn!("Unknown android_activity input event {event:?}")
|
||||
}
|
||||
}
|
||||
input_status
|
||||
|
||||
// Assume all events are handled, while Winit doesn't currently give a way for
|
||||
// applications to report whether they handled an input event.
|
||||
InputStatus::Handled
|
||||
});
|
||||
|
||||
// Empty the user event buffer
|
||||
@@ -872,7 +1004,7 @@ impl Window {
|
||||
|
||||
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
|
||||
|
||||
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
|
||||
pub fn set_ime_position(&self, _position: Position) {}
|
||||
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {}
|
||||
|
||||
@@ -952,8 +1084,6 @@ impl Window {
|
||||
pub fn title(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
pub fn reset_dead_keys(&self) {}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
|
||||
@@ -107,9 +107,6 @@ impl DeviceId {
|
||||
unsafe impl Send for DeviceId {}
|
||||
unsafe impl Sync for DeviceId {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventExtra {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OsError {}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ extern_methods!(
|
||||
);
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UIInterfaceOrientationMask: NSUInteger {
|
||||
const Portrait = 1 << 1;
|
||||
const PortraitUpsideDown = 1 << 2;
|
||||
|
||||
@@ -295,8 +295,8 @@ impl Inner {
|
||||
warn!("`Window::set_window_icon` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {
|
||||
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
|
||||
pub fn set_ime_position(&self, _position: Position) {
|
||||
warn!("`Window::set_ime_position` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {
|
||||
@@ -368,10 +368,6 @@ impl Inner {
|
||||
warn!("`Window::title` is ignored on iOS");
|
||||
String::new()
|
||||
}
|
||||
|
||||
pub fn reset_dead_keys(&self) {
|
||||
// Noop
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
|
||||
@@ -1,887 +0,0 @@
|
||||
//! Convert XKB keys to Winit keys.
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
|
||||
|
||||
/// Map the raw X11-style keycode to the `KeyCode` enum.
|
||||
///
|
||||
/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses.
|
||||
pub fn raw_keycode_to_keycode(keycode: u32) -> KeyCode {
|
||||
scancode_to_keycode(keycode.saturating_sub(8))
|
||||
}
|
||||
|
||||
/// Map the linux scancode to Keycode.
|
||||
///
|
||||
/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode.
|
||||
pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
|
||||
// The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as
|
||||
// libxkbcommon's documentation seems to suggest that the keycode values we're interested in
|
||||
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
|
||||
// I can only hope they agree on what the keycodes mean.
|
||||
//
|
||||
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are
|
||||
// difficult to test the correctness of, or discover the purpose of. Because of this, they've
|
||||
// either been commented out here, or not included at all.
|
||||
match scancode {
|
||||
0 => KeyCode::Unidentified(NativeKeyCode::Xkb(0)),
|
||||
1 => KeyCode::Escape,
|
||||
2 => KeyCode::Digit1,
|
||||
3 => KeyCode::Digit2,
|
||||
4 => KeyCode::Digit3,
|
||||
5 => KeyCode::Digit4,
|
||||
6 => KeyCode::Digit5,
|
||||
7 => KeyCode::Digit6,
|
||||
8 => KeyCode::Digit7,
|
||||
9 => KeyCode::Digit8,
|
||||
10 => KeyCode::Digit9,
|
||||
11 => KeyCode::Digit0,
|
||||
12 => KeyCode::Minus,
|
||||
13 => KeyCode::Equal,
|
||||
14 => KeyCode::Backspace,
|
||||
15 => KeyCode::Tab,
|
||||
16 => KeyCode::KeyQ,
|
||||
17 => KeyCode::KeyW,
|
||||
18 => KeyCode::KeyE,
|
||||
19 => KeyCode::KeyR,
|
||||
20 => KeyCode::KeyT,
|
||||
21 => KeyCode::KeyY,
|
||||
22 => KeyCode::KeyU,
|
||||
23 => KeyCode::KeyI,
|
||||
24 => KeyCode::KeyO,
|
||||
25 => KeyCode::KeyP,
|
||||
26 => KeyCode::BracketLeft,
|
||||
27 => KeyCode::BracketRight,
|
||||
28 => KeyCode::Enter,
|
||||
29 => KeyCode::ControlLeft,
|
||||
30 => KeyCode::KeyA,
|
||||
31 => KeyCode::KeyS,
|
||||
32 => KeyCode::KeyD,
|
||||
33 => KeyCode::KeyF,
|
||||
34 => KeyCode::KeyG,
|
||||
35 => KeyCode::KeyH,
|
||||
36 => KeyCode::KeyJ,
|
||||
37 => KeyCode::KeyK,
|
||||
38 => KeyCode::KeyL,
|
||||
39 => KeyCode::Semicolon,
|
||||
40 => KeyCode::Quote,
|
||||
41 => KeyCode::Backquote,
|
||||
42 => KeyCode::ShiftLeft,
|
||||
43 => KeyCode::Backslash,
|
||||
44 => KeyCode::KeyZ,
|
||||
45 => KeyCode::KeyX,
|
||||
46 => KeyCode::KeyC,
|
||||
47 => KeyCode::KeyV,
|
||||
48 => KeyCode::KeyB,
|
||||
49 => KeyCode::KeyN,
|
||||
50 => KeyCode::KeyM,
|
||||
51 => KeyCode::Comma,
|
||||
52 => KeyCode::Period,
|
||||
53 => KeyCode::Slash,
|
||||
54 => KeyCode::ShiftRight,
|
||||
55 => KeyCode::NumpadMultiply,
|
||||
56 => KeyCode::AltLeft,
|
||||
57 => KeyCode::Space,
|
||||
58 => KeyCode::CapsLock,
|
||||
59 => KeyCode::F1,
|
||||
60 => KeyCode::F2,
|
||||
61 => KeyCode::F3,
|
||||
62 => KeyCode::F4,
|
||||
63 => KeyCode::F5,
|
||||
64 => KeyCode::F6,
|
||||
65 => KeyCode::F7,
|
||||
66 => KeyCode::F8,
|
||||
67 => KeyCode::F9,
|
||||
68 => KeyCode::F10,
|
||||
69 => KeyCode::NumLock,
|
||||
70 => KeyCode::ScrollLock,
|
||||
71 => KeyCode::Numpad7,
|
||||
72 => KeyCode::Numpad8,
|
||||
73 => KeyCode::Numpad9,
|
||||
74 => KeyCode::NumpadSubtract,
|
||||
75 => KeyCode::Numpad4,
|
||||
76 => KeyCode::Numpad5,
|
||||
77 => KeyCode::Numpad6,
|
||||
78 => KeyCode::NumpadAdd,
|
||||
79 => KeyCode::Numpad1,
|
||||
80 => KeyCode::Numpad2,
|
||||
81 => KeyCode::Numpad3,
|
||||
82 => KeyCode::Numpad0,
|
||||
83 => KeyCode::NumpadDecimal,
|
||||
85 => KeyCode::Lang5,
|
||||
86 => KeyCode::IntlBackslash,
|
||||
87 => KeyCode::F11,
|
||||
88 => KeyCode::F12,
|
||||
89 => KeyCode::IntlRo,
|
||||
90 => KeyCode::Lang3,
|
||||
91 => KeyCode::Lang4,
|
||||
92 => KeyCode::Convert,
|
||||
93 => KeyCode::KanaMode,
|
||||
94 => KeyCode::NonConvert,
|
||||
// 95 => KeyCode::KPJPCOMMA,
|
||||
96 => KeyCode::NumpadEnter,
|
||||
97 => KeyCode::ControlRight,
|
||||
98 => KeyCode::NumpadDivide,
|
||||
99 => KeyCode::PrintScreen,
|
||||
100 => KeyCode::AltRight,
|
||||
// 101 => KeyCode::LINEFEED,
|
||||
102 => KeyCode::Home,
|
||||
103 => KeyCode::ArrowUp,
|
||||
104 => KeyCode::PageUp,
|
||||
105 => KeyCode::ArrowLeft,
|
||||
106 => KeyCode::ArrowRight,
|
||||
107 => KeyCode::End,
|
||||
108 => KeyCode::ArrowDown,
|
||||
109 => KeyCode::PageDown,
|
||||
110 => KeyCode::Insert,
|
||||
111 => KeyCode::Delete,
|
||||
// 112 => KeyCode::MACRO,
|
||||
113 => KeyCode::AudioVolumeMute,
|
||||
114 => KeyCode::AudioVolumeDown,
|
||||
115 => KeyCode::AudioVolumeUp,
|
||||
// 116 => KeyCode::POWER,
|
||||
117 => KeyCode::NumpadEqual,
|
||||
// 118 => KeyCode::KPPLUSMINUS,
|
||||
119 => KeyCode::Pause,
|
||||
// 120 => KeyCode::SCALE,
|
||||
121 => KeyCode::NumpadComma,
|
||||
122 => KeyCode::Lang1,
|
||||
123 => KeyCode::Lang2,
|
||||
124 => KeyCode::IntlYen,
|
||||
125 => KeyCode::SuperLeft,
|
||||
126 => KeyCode::SuperRight,
|
||||
127 => KeyCode::ContextMenu,
|
||||
// 128 => KeyCode::STOP,
|
||||
// 129 => KeyCode::AGAIN,
|
||||
// 130 => KeyCode::PROPS,
|
||||
// 131 => KeyCode::UNDO,
|
||||
// 132 => KeyCode::FRONT,
|
||||
// 133 => KeyCode::COPY,
|
||||
// 134 => KeyCode::OPEN,
|
||||
// 135 => KeyCode::PASTE,
|
||||
// 136 => KeyCode::FIND,
|
||||
// 137 => KeyCode::CUT,
|
||||
// 138 => KeyCode::HELP,
|
||||
// 139 => KeyCode::MENU,
|
||||
// 140 => KeyCode::CALC,
|
||||
// 141 => KeyCode::SETUP,
|
||||
// 142 => KeyCode::SLEEP,
|
||||
// 143 => KeyCode::WAKEUP,
|
||||
// 144 => KeyCode::FILE,
|
||||
// 145 => KeyCode::SENDFILE,
|
||||
// 146 => KeyCode::DELETEFILE,
|
||||
// 147 => KeyCode::XFER,
|
||||
// 148 => KeyCode::PROG1,
|
||||
// 149 => KeyCode::PROG2,
|
||||
// 150 => KeyCode::WWW,
|
||||
// 151 => KeyCode::MSDOS,
|
||||
// 152 => KeyCode::COFFEE,
|
||||
// 153 => KeyCode::ROTATE_DISPLAY,
|
||||
// 154 => KeyCode::CYCLEWINDOWS,
|
||||
// 155 => KeyCode::MAIL,
|
||||
// 156 => KeyCode::BOOKMARKS,
|
||||
// 157 => KeyCode::COMPUTER,
|
||||
// 158 => KeyCode::BACK,
|
||||
// 159 => KeyCode::FORWARD,
|
||||
// 160 => KeyCode::CLOSECD,
|
||||
// 161 => KeyCode::EJECTCD,
|
||||
// 162 => KeyCode::EJECTCLOSECD,
|
||||
163 => KeyCode::MediaTrackNext,
|
||||
164 => KeyCode::MediaPlayPause,
|
||||
165 => KeyCode::MediaTrackPrevious,
|
||||
166 => KeyCode::MediaStop,
|
||||
// 167 => KeyCode::RECORD,
|
||||
// 168 => KeyCode::REWIND,
|
||||
// 169 => KeyCode::PHONE,
|
||||
// 170 => KeyCode::ISO,
|
||||
// 171 => KeyCode::CONFIG,
|
||||
// 172 => KeyCode::HOMEPAGE,
|
||||
// 173 => KeyCode::REFRESH,
|
||||
// 174 => KeyCode::EXIT,
|
||||
// 175 => KeyCode::MOVE,
|
||||
// 176 => KeyCode::EDIT,
|
||||
// 177 => KeyCode::SCROLLUP,
|
||||
// 178 => KeyCode::SCROLLDOWN,
|
||||
// 179 => KeyCode::KPLEFTPAREN,
|
||||
// 180 => KeyCode::KPRIGHTPAREN,
|
||||
// 181 => KeyCode::NEW,
|
||||
// 182 => KeyCode::REDO,
|
||||
183 => KeyCode::F13,
|
||||
184 => KeyCode::F14,
|
||||
185 => KeyCode::F15,
|
||||
186 => KeyCode::F16,
|
||||
187 => KeyCode::F17,
|
||||
188 => KeyCode::F18,
|
||||
189 => KeyCode::F19,
|
||||
190 => KeyCode::F20,
|
||||
191 => KeyCode::F21,
|
||||
192 => KeyCode::F22,
|
||||
193 => KeyCode::F23,
|
||||
194 => KeyCode::F24,
|
||||
// 200 => KeyCode::PLAYCD,
|
||||
// 201 => KeyCode::PAUSECD,
|
||||
// 202 => KeyCode::PROG3,
|
||||
// 203 => KeyCode::PROG4,
|
||||
// 204 => KeyCode::DASHBOARD,
|
||||
// 205 => KeyCode::SUSPEND,
|
||||
// 206 => KeyCode::CLOSE,
|
||||
// 207 => KeyCode::PLAY,
|
||||
// 208 => KeyCode::FASTFORWARD,
|
||||
// 209 => KeyCode::BASSBOOST,
|
||||
// 210 => KeyCode::PRINT,
|
||||
// 211 => KeyCode::HP,
|
||||
// 212 => KeyCode::CAMERA,
|
||||
// 213 => KeyCode::SOUND,
|
||||
// 214 => KeyCode::QUESTION,
|
||||
// 215 => KeyCode::EMAIL,
|
||||
// 216 => KeyCode::CHAT,
|
||||
// 217 => KeyCode::SEARCH,
|
||||
// 218 => KeyCode::CONNECT,
|
||||
// 219 => KeyCode::FINANCE,
|
||||
// 220 => KeyCode::SPORT,
|
||||
// 221 => KeyCode::SHOP,
|
||||
// 222 => KeyCode::ALTERASE,
|
||||
// 223 => KeyCode::CANCEL,
|
||||
// 224 => KeyCode::BRIGHTNESSDOW,
|
||||
// 225 => KeyCode::BRIGHTNESSU,
|
||||
// 226 => KeyCode::MEDIA,
|
||||
// 227 => KeyCode::SWITCHVIDEOMODE,
|
||||
// 228 => KeyCode::KBDILLUMTOGGLE,
|
||||
// 229 => KeyCode::KBDILLUMDOWN,
|
||||
// 230 => KeyCode::KBDILLUMUP,
|
||||
// 231 => KeyCode::SEND,
|
||||
// 232 => KeyCode::REPLY,
|
||||
// 233 => KeyCode::FORWARDMAIL,
|
||||
// 234 => KeyCode::SAVE,
|
||||
// 235 => KeyCode::DOCUMENTS,
|
||||
// 236 => KeyCode::BATTERY,
|
||||
// 237 => KeyCode::BLUETOOTH,
|
||||
// 238 => KeyCode::WLAN,
|
||||
// 239 => KeyCode::UWB,
|
||||
240 => KeyCode::Unidentified(NativeKeyCode::Unidentified),
|
||||
// 241 => KeyCode::VIDEO_NEXT,
|
||||
// 242 => KeyCode::VIDEO_PREV,
|
||||
// 243 => KeyCode::BRIGHTNESS_CYCLE,
|
||||
// 244 => KeyCode::BRIGHTNESS_AUTO,
|
||||
// 245 => KeyCode::DISPLAY_OFF,
|
||||
// 246 => KeyCode::WWAN,
|
||||
// 247 => KeyCode::RFKILL,
|
||||
// 248 => KeyCode::KEY_MICMUTE,
|
||||
_ => KeyCode::Unidentified(NativeKeyCode::Xkb(scancode)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keycode_to_scancode(keycode: KeyCode) -> Option<u32> {
|
||||
match keycode {
|
||||
KeyCode::Unidentified(NativeKeyCode::Unidentified) => Some(240),
|
||||
KeyCode::Unidentified(NativeKeyCode::Xkb(raw)) => Some(raw),
|
||||
KeyCode::Escape => Some(1),
|
||||
KeyCode::Digit1 => Some(2),
|
||||
KeyCode::Digit2 => Some(3),
|
||||
KeyCode::Digit3 => Some(4),
|
||||
KeyCode::Digit4 => Some(5),
|
||||
KeyCode::Digit5 => Some(6),
|
||||
KeyCode::Digit6 => Some(7),
|
||||
KeyCode::Digit7 => Some(8),
|
||||
KeyCode::Digit8 => Some(9),
|
||||
KeyCode::Digit9 => Some(10),
|
||||
KeyCode::Digit0 => Some(11),
|
||||
KeyCode::Minus => Some(12),
|
||||
KeyCode::Equal => Some(13),
|
||||
KeyCode::Backspace => Some(14),
|
||||
KeyCode::Tab => Some(15),
|
||||
KeyCode::KeyQ => Some(16),
|
||||
KeyCode::KeyW => Some(17),
|
||||
KeyCode::KeyE => Some(18),
|
||||
KeyCode::KeyR => Some(19),
|
||||
KeyCode::KeyT => Some(20),
|
||||
KeyCode::KeyY => Some(21),
|
||||
KeyCode::KeyU => Some(22),
|
||||
KeyCode::KeyI => Some(23),
|
||||
KeyCode::KeyO => Some(24),
|
||||
KeyCode::KeyP => Some(25),
|
||||
KeyCode::BracketLeft => Some(26),
|
||||
KeyCode::BracketRight => Some(27),
|
||||
KeyCode::Enter => Some(28),
|
||||
KeyCode::ControlLeft => Some(29),
|
||||
KeyCode::KeyA => Some(30),
|
||||
KeyCode::KeyS => Some(31),
|
||||
KeyCode::KeyD => Some(32),
|
||||
KeyCode::KeyF => Some(33),
|
||||
KeyCode::KeyG => Some(34),
|
||||
KeyCode::KeyH => Some(35),
|
||||
KeyCode::KeyJ => Some(36),
|
||||
KeyCode::KeyK => Some(37),
|
||||
KeyCode::KeyL => Some(38),
|
||||
KeyCode::Semicolon => Some(39),
|
||||
KeyCode::Quote => Some(40),
|
||||
KeyCode::Backquote => Some(41),
|
||||
KeyCode::ShiftLeft => Some(42),
|
||||
KeyCode::Backslash => Some(43),
|
||||
KeyCode::KeyZ => Some(44),
|
||||
KeyCode::KeyX => Some(45),
|
||||
KeyCode::KeyC => Some(46),
|
||||
KeyCode::KeyV => Some(47),
|
||||
KeyCode::KeyB => Some(48),
|
||||
KeyCode::KeyN => Some(49),
|
||||
KeyCode::KeyM => Some(50),
|
||||
KeyCode::Comma => Some(51),
|
||||
KeyCode::Period => Some(52),
|
||||
KeyCode::Slash => Some(53),
|
||||
KeyCode::ShiftRight => Some(54),
|
||||
KeyCode::NumpadMultiply => Some(55),
|
||||
KeyCode::AltLeft => Some(56),
|
||||
KeyCode::Space => Some(57),
|
||||
KeyCode::CapsLock => Some(58),
|
||||
KeyCode::F1 => Some(59),
|
||||
KeyCode::F2 => Some(60),
|
||||
KeyCode::F3 => Some(61),
|
||||
KeyCode::F4 => Some(62),
|
||||
KeyCode::F5 => Some(63),
|
||||
KeyCode::F6 => Some(64),
|
||||
KeyCode::F7 => Some(65),
|
||||
KeyCode::F8 => Some(66),
|
||||
KeyCode::F9 => Some(67),
|
||||
KeyCode::F10 => Some(68),
|
||||
KeyCode::NumLock => Some(69),
|
||||
KeyCode::ScrollLock => Some(70),
|
||||
KeyCode::Numpad7 => Some(71),
|
||||
KeyCode::Numpad8 => Some(72),
|
||||
KeyCode::Numpad9 => Some(73),
|
||||
KeyCode::NumpadSubtract => Some(74),
|
||||
KeyCode::Numpad4 => Some(75),
|
||||
KeyCode::Numpad5 => Some(76),
|
||||
KeyCode::Numpad6 => Some(77),
|
||||
KeyCode::NumpadAdd => Some(78),
|
||||
KeyCode::Numpad1 => Some(79),
|
||||
KeyCode::Numpad2 => Some(80),
|
||||
KeyCode::Numpad3 => Some(81),
|
||||
KeyCode::Numpad0 => Some(82),
|
||||
KeyCode::NumpadDecimal => Some(83),
|
||||
KeyCode::Lang5 => Some(85),
|
||||
KeyCode::IntlBackslash => Some(86),
|
||||
KeyCode::F11 => Some(87),
|
||||
KeyCode::F12 => Some(88),
|
||||
KeyCode::IntlRo => Some(89),
|
||||
KeyCode::Lang3 => Some(90),
|
||||
KeyCode::Lang4 => Some(91),
|
||||
KeyCode::Convert => Some(92),
|
||||
KeyCode::KanaMode => Some(93),
|
||||
KeyCode::NonConvert => Some(94),
|
||||
KeyCode::NumpadEnter => Some(96),
|
||||
KeyCode::ControlRight => Some(97),
|
||||
KeyCode::NumpadDivide => Some(98),
|
||||
KeyCode::PrintScreen => Some(99),
|
||||
KeyCode::AltRight => Some(100),
|
||||
KeyCode::Home => Some(102),
|
||||
KeyCode::ArrowUp => Some(103),
|
||||
KeyCode::PageUp => Some(104),
|
||||
KeyCode::ArrowLeft => Some(105),
|
||||
KeyCode::ArrowRight => Some(106),
|
||||
KeyCode::End => Some(107),
|
||||
KeyCode::ArrowDown => Some(108),
|
||||
KeyCode::PageDown => Some(109),
|
||||
KeyCode::Insert => Some(110),
|
||||
KeyCode::Delete => Some(111),
|
||||
KeyCode::AudioVolumeMute => Some(113),
|
||||
KeyCode::AudioVolumeDown => Some(114),
|
||||
KeyCode::AudioVolumeUp => Some(115),
|
||||
KeyCode::NumpadEqual => Some(117),
|
||||
KeyCode::Pause => Some(119),
|
||||
KeyCode::NumpadComma => Some(121),
|
||||
KeyCode::Lang1 => Some(122),
|
||||
KeyCode::Lang2 => Some(123),
|
||||
KeyCode::IntlYen => Some(124),
|
||||
KeyCode::SuperLeft => Some(125),
|
||||
KeyCode::SuperRight => Some(126),
|
||||
KeyCode::ContextMenu => Some(127),
|
||||
KeyCode::MediaTrackNext => Some(163),
|
||||
KeyCode::MediaPlayPause => Some(164),
|
||||
KeyCode::MediaTrackPrevious => Some(165),
|
||||
KeyCode::MediaStop => Some(166),
|
||||
KeyCode::F13 => Some(183),
|
||||
KeyCode::F14 => Some(184),
|
||||
KeyCode::F15 => Some(185),
|
||||
KeyCode::F16 => Some(186),
|
||||
KeyCode::F17 => Some(187),
|
||||
KeyCode::F18 => Some(188),
|
||||
KeyCode::F19 => Some(189),
|
||||
KeyCode::F20 => Some(190),
|
||||
KeyCode::F21 => Some(191),
|
||||
KeyCode::F22 => Some(192),
|
||||
KeyCode::F23 => Some(193),
|
||||
KeyCode::F24 => Some(194),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keysym_to_key(keysym: u32) -> Key {
|
||||
use xkbcommon_dl::keysyms;
|
||||
match keysym {
|
||||
// TTY function keys
|
||||
keysyms::BackSpace => Key::Backspace,
|
||||
keysyms::Tab => Key::Tab,
|
||||
// keysyms::Linefeed => Key::Linefeed,
|
||||
keysyms::Clear => Key::Clear,
|
||||
keysyms::Return => Key::Enter,
|
||||
keysyms::Pause => Key::Pause,
|
||||
keysyms::Scroll_Lock => Key::ScrollLock,
|
||||
keysyms::Sys_Req => Key::PrintScreen,
|
||||
keysyms::Escape => Key::Escape,
|
||||
keysyms::Delete => Key::Delete,
|
||||
|
||||
// IME keys
|
||||
keysyms::Multi_key => Key::Compose,
|
||||
keysyms::Codeinput => Key::CodeInput,
|
||||
keysyms::SingleCandidate => Key::SingleCandidate,
|
||||
keysyms::MultipleCandidate => Key::AllCandidates,
|
||||
keysyms::PreviousCandidate => Key::PreviousCandidate,
|
||||
|
||||
// Japanese keys
|
||||
keysyms::Kanji => Key::KanjiMode,
|
||||
keysyms::Muhenkan => Key::NonConvert,
|
||||
keysyms::Henkan_Mode => Key::Convert,
|
||||
keysyms::Romaji => Key::Romaji,
|
||||
keysyms::Hiragana => Key::Hiragana,
|
||||
keysyms::Hiragana_Katakana => Key::HiraganaKatakana,
|
||||
keysyms::Zenkaku => Key::Zenkaku,
|
||||
keysyms::Hankaku => Key::Hankaku,
|
||||
keysyms::Zenkaku_Hankaku => Key::ZenkakuHankaku,
|
||||
// keysyms::Touroku => Key::Touroku,
|
||||
// keysyms::Massyo => Key::Massyo,
|
||||
keysyms::Kana_Lock => Key::KanaMode,
|
||||
keysyms::Kana_Shift => Key::KanaMode,
|
||||
keysyms::Eisu_Shift => Key::Alphanumeric,
|
||||
keysyms::Eisu_toggle => Key::Alphanumeric,
|
||||
// NOTE: The next three items are aliases for values we've already mapped.
|
||||
// keysyms::Kanji_Bangou => Key::CodeInput,
|
||||
// keysyms::Zen_Koho => Key::AllCandidates,
|
||||
// keysyms::Mae_Koho => Key::PreviousCandidate,
|
||||
|
||||
// Cursor control & motion
|
||||
keysyms::Home => Key::Home,
|
||||
keysyms::Left => Key::ArrowLeft,
|
||||
keysyms::Up => Key::ArrowUp,
|
||||
keysyms::Right => Key::ArrowRight,
|
||||
keysyms::Down => Key::ArrowDown,
|
||||
// keysyms::Prior => Key::PageUp,
|
||||
keysyms::Page_Up => Key::PageUp,
|
||||
// keysyms::Next => Key::PageDown,
|
||||
keysyms::Page_Down => Key::PageDown,
|
||||
keysyms::End => Key::End,
|
||||
// keysyms::Begin => Key::Begin,
|
||||
|
||||
// Misc. functions
|
||||
keysyms::Select => Key::Select,
|
||||
keysyms::Print => Key::PrintScreen,
|
||||
keysyms::Execute => Key::Execute,
|
||||
keysyms::Insert => Key::Insert,
|
||||
keysyms::Undo => Key::Undo,
|
||||
keysyms::Redo => Key::Redo,
|
||||
keysyms::Menu => Key::ContextMenu,
|
||||
keysyms::Find => Key::Find,
|
||||
keysyms::Cancel => Key::Cancel,
|
||||
keysyms::Help => Key::Help,
|
||||
keysyms::Break => Key::Pause,
|
||||
keysyms::Mode_switch => Key::ModeChange,
|
||||
// keysyms::script_switch => Key::ModeChange,
|
||||
keysyms::Num_Lock => Key::NumLock,
|
||||
|
||||
// Keypad keys
|
||||
// keysyms::KP_Space => Key::Character(" "),
|
||||
keysyms::KP_Tab => Key::Tab,
|
||||
keysyms::KP_Enter => Key::Enter,
|
||||
keysyms::KP_F1 => Key::F1,
|
||||
keysyms::KP_F2 => Key::F2,
|
||||
keysyms::KP_F3 => Key::F3,
|
||||
keysyms::KP_F4 => Key::F4,
|
||||
keysyms::KP_Home => Key::Home,
|
||||
keysyms::KP_Left => Key::ArrowLeft,
|
||||
keysyms::KP_Up => Key::ArrowLeft,
|
||||
keysyms::KP_Right => Key::ArrowRight,
|
||||
keysyms::KP_Down => Key::ArrowDown,
|
||||
// keysyms::KP_Prior => Key::PageUp,
|
||||
keysyms::KP_Page_Up => Key::PageUp,
|
||||
// keysyms::KP_Next => Key::PageDown,
|
||||
keysyms::KP_Page_Down => Key::PageDown,
|
||||
keysyms::KP_End => Key::End,
|
||||
// This is the key labeled "5" on the numpad when NumLock is off.
|
||||
// keysyms::KP_Begin => Key::Begin,
|
||||
keysyms::KP_Insert => Key::Insert,
|
||||
keysyms::KP_Delete => Key::Delete,
|
||||
// keysyms::KP_Equal => Key::Equal,
|
||||
// keysyms::KP_Multiply => Key::Multiply,
|
||||
// keysyms::KP_Add => Key::Add,
|
||||
// keysyms::KP_Separator => Key::Separator,
|
||||
// keysyms::KP_Subtract => Key::Subtract,
|
||||
// keysyms::KP_Decimal => Key::Decimal,
|
||||
// keysyms::KP_Divide => Key::Divide,
|
||||
|
||||
// keysyms::KP_0 => Key::Character("0"),
|
||||
// keysyms::KP_1 => Key::Character("1"),
|
||||
// keysyms::KP_2 => Key::Character("2"),
|
||||
// keysyms::KP_3 => Key::Character("3"),
|
||||
// keysyms::KP_4 => Key::Character("4"),
|
||||
// keysyms::KP_5 => Key::Character("5"),
|
||||
// keysyms::KP_6 => Key::Character("6"),
|
||||
// keysyms::KP_7 => Key::Character("7"),
|
||||
// keysyms::KP_8 => Key::Character("8"),
|
||||
// keysyms::KP_9 => Key::Character("9"),
|
||||
|
||||
// Function keys
|
||||
keysyms::F1 => Key::F1,
|
||||
keysyms::F2 => Key::F2,
|
||||
keysyms::F3 => Key::F3,
|
||||
keysyms::F4 => Key::F4,
|
||||
keysyms::F5 => Key::F5,
|
||||
keysyms::F6 => Key::F6,
|
||||
keysyms::F7 => Key::F7,
|
||||
keysyms::F8 => Key::F8,
|
||||
keysyms::F9 => Key::F9,
|
||||
keysyms::F10 => Key::F10,
|
||||
keysyms::F11 => Key::F11,
|
||||
keysyms::F12 => Key::F12,
|
||||
keysyms::F13 => Key::F13,
|
||||
keysyms::F14 => Key::F14,
|
||||
keysyms::F15 => Key::F15,
|
||||
keysyms::F16 => Key::F16,
|
||||
keysyms::F17 => Key::F17,
|
||||
keysyms::F18 => Key::F18,
|
||||
keysyms::F19 => Key::F19,
|
||||
keysyms::F20 => Key::F20,
|
||||
keysyms::F21 => Key::F21,
|
||||
keysyms::F22 => Key::F22,
|
||||
keysyms::F23 => Key::F23,
|
||||
keysyms::F24 => Key::F24,
|
||||
keysyms::F25 => Key::F25,
|
||||
keysyms::F26 => Key::F26,
|
||||
keysyms::F27 => Key::F27,
|
||||
keysyms::F28 => Key::F28,
|
||||
keysyms::F29 => Key::F29,
|
||||
keysyms::F30 => Key::F30,
|
||||
keysyms::F31 => Key::F31,
|
||||
keysyms::F32 => Key::F32,
|
||||
keysyms::F33 => Key::F33,
|
||||
keysyms::F34 => Key::F34,
|
||||
keysyms::F35 => Key::F35,
|
||||
|
||||
// Modifiers
|
||||
keysyms::Shift_L => Key::Shift,
|
||||
keysyms::Shift_R => Key::Shift,
|
||||
keysyms::Control_L => Key::Control,
|
||||
keysyms::Control_R => Key::Control,
|
||||
keysyms::Caps_Lock => Key::CapsLock,
|
||||
// keysyms::Shift_Lock => Key::ShiftLock,
|
||||
|
||||
// keysyms::Meta_L => Key::Meta,
|
||||
// keysyms::Meta_R => Key::Meta,
|
||||
keysyms::Alt_L => Key::Alt,
|
||||
keysyms::Alt_R => Key::Alt,
|
||||
keysyms::Super_L => Key::Super,
|
||||
keysyms::Super_R => Key::Super,
|
||||
keysyms::Hyper_L => Key::Hyper,
|
||||
keysyms::Hyper_R => Key::Hyper,
|
||||
|
||||
// XKB function and modifier keys
|
||||
// keysyms::ISO_Lock => Key::IsoLock,
|
||||
// keysyms::ISO_Level2_Latch => Key::IsoLevel2Latch,
|
||||
keysyms::ISO_Level3_Shift => Key::AltGraph,
|
||||
keysyms::ISO_Level3_Latch => Key::AltGraph,
|
||||
keysyms::ISO_Level3_Lock => Key::AltGraph,
|
||||
// keysyms::ISO_Level5_Shift => Key::IsoLevel5Shift,
|
||||
// keysyms::ISO_Level5_Latch => Key::IsoLevel5Latch,
|
||||
// keysyms::ISO_Level5_Lock => Key::IsoLevel5Lock,
|
||||
// keysyms::ISO_Group_Shift => Key::IsoGroupShift,
|
||||
// keysyms::ISO_Group_Latch => Key::IsoGroupLatch,
|
||||
// keysyms::ISO_Group_Lock => Key::IsoGroupLock,
|
||||
keysyms::ISO_Next_Group => Key::GroupNext,
|
||||
// keysyms::ISO_Next_Group_Lock => Key::GroupNextLock,
|
||||
keysyms::ISO_Prev_Group => Key::GroupPrevious,
|
||||
// keysyms::ISO_Prev_Group_Lock => Key::GroupPreviousLock,
|
||||
keysyms::ISO_First_Group => Key::GroupFirst,
|
||||
// keysyms::ISO_First_Group_Lock => Key::GroupFirstLock,
|
||||
keysyms::ISO_Last_Group => Key::GroupLast,
|
||||
// keysyms::ISO_Last_Group_Lock => Key::GroupLastLock,
|
||||
//
|
||||
keysyms::ISO_Left_Tab => Key::Tab,
|
||||
// keysyms::ISO_Move_Line_Up => Key::IsoMoveLineUp,
|
||||
// keysyms::ISO_Move_Line_Down => Key::IsoMoveLineDown,
|
||||
// keysyms::ISO_Partial_Line_Up => Key::IsoPartialLineUp,
|
||||
// keysyms::ISO_Partial_Line_Down => Key::IsoPartialLineDown,
|
||||
// keysyms::ISO_Partial_Space_Left => Key::IsoPartialSpaceLeft,
|
||||
// keysyms::ISO_Partial_Space_Right => Key::IsoPartialSpaceRight,
|
||||
// keysyms::ISO_Set_Margin_Left => Key::IsoSetMarginLeft,
|
||||
// keysyms::ISO_Set_Margin_Right => Key::IsoSetMarginRight,
|
||||
// keysyms::ISO_Release_Margin_Left => Key::IsoReleaseMarginLeft,
|
||||
// keysyms::ISO_Release_Margin_Right => Key::IsoReleaseMarginRight,
|
||||
// keysyms::ISO_Release_Both_Margins => Key::IsoReleaseBothMargins,
|
||||
// keysyms::ISO_Fast_Cursor_Left => Key::IsoFastCursorLeft,
|
||||
// keysyms::ISO_Fast_Cursor_Right => Key::IsoFastCursorRight,
|
||||
// keysyms::ISO_Fast_Cursor_Up => Key::IsoFastCursorUp,
|
||||
// keysyms::ISO_Fast_Cursor_Down => Key::IsoFastCursorDown,
|
||||
// keysyms::ISO_Continuous_Underline => Key::IsoContinuousUnderline,
|
||||
// keysyms::ISO_Discontinuous_Underline => Key::IsoDiscontinuousUnderline,
|
||||
// keysyms::ISO_Emphasize => Key::IsoEmphasize,
|
||||
// keysyms::ISO_Center_Object => Key::IsoCenterObject,
|
||||
keysyms::ISO_Enter => Key::Enter,
|
||||
|
||||
// dead_grave..dead_currency
|
||||
|
||||
// dead_lowline..dead_longsolidusoverlay
|
||||
|
||||
// dead_a..dead_capital_schwa
|
||||
|
||||
// dead_greek
|
||||
|
||||
// First_Virtual_Screen..Terminate_Server
|
||||
|
||||
// AccessX_Enable..AudibleBell_Enable
|
||||
|
||||
// Pointer_Left..Pointer_Drag5
|
||||
|
||||
// Pointer_EnableKeys..Pointer_DfltBtnPrev
|
||||
|
||||
// ch..C_H
|
||||
|
||||
// 3270 terminal keys
|
||||
// keysyms::3270_Duplicate => Key::Duplicate,
|
||||
// keysyms::3270_FieldMark => Key::FieldMark,
|
||||
// keysyms::3270_Right2 => Key::Right2,
|
||||
// keysyms::3270_Left2 => Key::Left2,
|
||||
// keysyms::3270_BackTab => Key::BackTab,
|
||||
keysyms::_3270_EraseEOF => Key::EraseEof,
|
||||
// keysyms::3270_EraseInput => Key::EraseInput,
|
||||
// keysyms::3270_Reset => Key::Reset,
|
||||
// keysyms::3270_Quit => Key::Quit,
|
||||
// keysyms::3270_PA1 => Key::Pa1,
|
||||
// keysyms::3270_PA2 => Key::Pa2,
|
||||
// keysyms::3270_PA3 => Key::Pa3,
|
||||
// keysyms::3270_Test => Key::Test,
|
||||
keysyms::_3270_Attn => Key::Attn,
|
||||
// keysyms::3270_CursorBlink => Key::CursorBlink,
|
||||
// keysyms::3270_AltCursor => Key::AltCursor,
|
||||
// keysyms::3270_KeyClick => Key::KeyClick,
|
||||
// keysyms::3270_Jump => Key::Jump,
|
||||
// keysyms::3270_Ident => Key::Ident,
|
||||
// keysyms::3270_Rule => Key::Rule,
|
||||
// keysyms::3270_Copy => Key::Copy,
|
||||
keysyms::_3270_Play => Key::Play,
|
||||
// keysyms::3270_Setup => Key::Setup,
|
||||
// keysyms::3270_Record => Key::Record,
|
||||
// keysyms::3270_ChangeScreen => Key::ChangeScreen,
|
||||
// keysyms::3270_DeleteWord => Key::DeleteWord,
|
||||
keysyms::_3270_ExSelect => Key::ExSel,
|
||||
keysyms::_3270_CursorSelect => Key::CrSel,
|
||||
keysyms::_3270_PrintScreen => Key::PrintScreen,
|
||||
keysyms::_3270_Enter => Key::Enter,
|
||||
|
||||
keysyms::space => Key::Space,
|
||||
// exclam..Sinh_kunddaliya
|
||||
|
||||
// XFree86
|
||||
// keysyms::XF86_ModeLock => Key::ModeLock,
|
||||
|
||||
// XFree86 - Backlight controls
|
||||
keysyms::XF86_MonBrightnessUp => Key::BrightnessUp,
|
||||
keysyms::XF86_MonBrightnessDown => Key::BrightnessDown,
|
||||
// keysyms::XF86_KbdLightOnOff => Key::LightOnOff,
|
||||
// keysyms::XF86_KbdBrightnessUp => Key::KeyboardBrightnessUp,
|
||||
// keysyms::XF86_KbdBrightnessDown => Key::KeyboardBrightnessDown,
|
||||
|
||||
// XFree86 - "Internet"
|
||||
keysyms::XF86_Standby => Key::Standby,
|
||||
keysyms::XF86_AudioLowerVolume => Key::AudioVolumeDown,
|
||||
keysyms::XF86_AudioRaiseVolume => Key::AudioVolumeUp,
|
||||
keysyms::XF86_AudioPlay => Key::MediaPlay,
|
||||
keysyms::XF86_AudioStop => Key::MediaStop,
|
||||
keysyms::XF86_AudioPrev => Key::MediaTrackPrevious,
|
||||
keysyms::XF86_AudioNext => Key::MediaTrackNext,
|
||||
keysyms::XF86_HomePage => Key::BrowserHome,
|
||||
keysyms::XF86_Mail => Key::LaunchMail,
|
||||
// keysyms::XF86_Start => Key::Start,
|
||||
keysyms::XF86_Search => Key::BrowserSearch,
|
||||
keysyms::XF86_AudioRecord => Key::MediaRecord,
|
||||
|
||||
// XFree86 - PDA
|
||||
keysyms::XF86_Calculator => Key::LaunchApplication2,
|
||||
// keysyms::XF86_Memo => Key::Memo,
|
||||
// keysyms::XF86_ToDoList => Key::ToDoList,
|
||||
keysyms::XF86_Calendar => Key::LaunchCalendar,
|
||||
keysyms::XF86_PowerDown => Key::Power,
|
||||
// keysyms::XF86_ContrastAdjust => Key::AdjustContrast,
|
||||
// keysyms::XF86_RockerUp => Key::RockerUp,
|
||||
// keysyms::XF86_RockerDown => Key::RockerDown,
|
||||
// keysyms::XF86_RockerEnter => Key::RockerEnter,
|
||||
|
||||
// XFree86 - More "Internet"
|
||||
keysyms::XF86_Back => Key::BrowserBack,
|
||||
keysyms::XF86_Forward => Key::BrowserForward,
|
||||
// keysyms::XF86_Stop => Key::Stop,
|
||||
keysyms::XF86_Refresh => Key::BrowserRefresh,
|
||||
keysyms::XF86_PowerOff => Key::Power,
|
||||
keysyms::XF86_WakeUp => Key::WakeUp,
|
||||
keysyms::XF86_Eject => Key::Eject,
|
||||
keysyms::XF86_ScreenSaver => Key::LaunchScreenSaver,
|
||||
keysyms::XF86_WWW => Key::LaunchWebBrowser,
|
||||
keysyms::XF86_Sleep => Key::Standby,
|
||||
keysyms::XF86_Favorites => Key::BrowserFavorites,
|
||||
keysyms::XF86_AudioPause => Key::MediaPause,
|
||||
// keysyms::XF86_AudioMedia => Key::AudioMedia,
|
||||
keysyms::XF86_MyComputer => Key::LaunchApplication1,
|
||||
// keysyms::XF86_VendorHome => Key::VendorHome,
|
||||
// keysyms::XF86_LightBulb => Key::LightBulb,
|
||||
// keysyms::XF86_Shop => Key::BrowserShop,
|
||||
// keysyms::XF86_History => Key::BrowserHistory,
|
||||
// keysyms::XF86_OpenURL => Key::OpenUrl,
|
||||
// keysyms::XF86_AddFavorite => Key::AddFavorite,
|
||||
// keysyms::XF86_HotLinks => Key::HotLinks,
|
||||
// keysyms::XF86_BrightnessAdjust => Key::BrightnessAdjust,
|
||||
// keysyms::XF86_Finance => Key::BrowserFinance,
|
||||
// keysyms::XF86_Community => Key::BrowserCommunity,
|
||||
keysyms::XF86_AudioRewind => Key::MediaRewind,
|
||||
// keysyms::XF86_BackForward => Key::???,
|
||||
// XF86_Launch0..XF86_LaunchF
|
||||
|
||||
// XF86_ApplicationLeft..XF86_CD
|
||||
keysyms::XF86_Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :)
|
||||
// XF86_Clear
|
||||
keysyms::XF86_Close => Key::Close,
|
||||
keysyms::XF86_Copy => Key::Copy,
|
||||
keysyms::XF86_Cut => Key::Cut,
|
||||
// XF86_Display..XF86_Documents
|
||||
keysyms::XF86_Excel => Key::LaunchSpreadsheet,
|
||||
// XF86_Explorer..XF86iTouch
|
||||
keysyms::XF86_LogOff => Key::LogOff,
|
||||
// XF86_Market..XF86_MenuPB
|
||||
keysyms::XF86_MySites => Key::BrowserFavorites,
|
||||
keysyms::XF86_New => Key::New,
|
||||
// XF86_News..XF86_OfficeHome
|
||||
keysyms::XF86_Open => Key::Open,
|
||||
// XF86_Option
|
||||
keysyms::XF86_Paste => Key::Paste,
|
||||
keysyms::XF86_Phone => Key::LaunchPhone,
|
||||
// XF86_Q
|
||||
keysyms::XF86_Reply => Key::MailReply,
|
||||
keysyms::XF86_Reload => Key::BrowserRefresh,
|
||||
// XF86_RotateWindows..XF86_RotationKB
|
||||
keysyms::XF86_Save => Key::Save,
|
||||
// XF86_ScrollUp..XF86_ScrollClick
|
||||
keysyms::XF86_Send => Key::MailSend,
|
||||
keysyms::XF86_Spell => Key::SpellCheck,
|
||||
keysyms::XF86_SplitScreen => Key::SplitScreenToggle,
|
||||
// XF86_Support..XF86_User2KB
|
||||
keysyms::XF86_Video => Key::LaunchMediaPlayer,
|
||||
// XF86_WheelButton
|
||||
keysyms::XF86_Word => Key::LaunchWordProcessor,
|
||||
// XF86_Xfer
|
||||
keysyms::XF86_ZoomIn => Key::ZoomIn,
|
||||
keysyms::XF86_ZoomOut => Key::ZoomOut,
|
||||
|
||||
// XF86_Away..XF86_Messenger
|
||||
keysyms::XF86_WebCam => Key::LaunchWebCam,
|
||||
keysyms::XF86_MailForward => Key::MailForward,
|
||||
// XF86_Pictures
|
||||
keysyms::XF86_Music => Key::LaunchMusicPlayer,
|
||||
|
||||
// XF86_Battery..XF86_UWB
|
||||
//
|
||||
keysyms::XF86_AudioForward => Key::MediaFastForward,
|
||||
// XF86_AudioRepeat
|
||||
keysyms::XF86_AudioRandomPlay => Key::RandomToggle,
|
||||
keysyms::XF86_Subtitle => Key::Subtitle,
|
||||
keysyms::XF86_AudioCycleTrack => Key::MediaAudioTrack,
|
||||
// XF86_CycleAngle..XF86_Blue
|
||||
//
|
||||
keysyms::XF86_Suspend => Key::Standby,
|
||||
keysyms::XF86_Hibernate => Key::Hibernate,
|
||||
// XF86_TouchpadToggle..XF86_TouchpadOff
|
||||
//
|
||||
keysyms::XF86_AudioMute => Key::AudioVolumeMute,
|
||||
|
||||
// XF86_Switch_VT_1..XF86_Switch_VT_12
|
||||
|
||||
// XF86_Ungrab..XF86_ClearGrab
|
||||
keysyms::XF86_Next_VMode => Key::VideoModeNext,
|
||||
// keysyms::XF86_Prev_VMode => Key::VideoModePrevious,
|
||||
// XF86_LogWindowTree..XF86_LogGrabInfo
|
||||
|
||||
// SunFA_Grave..SunFA_Cedilla
|
||||
|
||||
// keysyms::SunF36 => Key::F36 | Key::F11,
|
||||
// keysyms::SunF37 => Key::F37 | Key::F12,
|
||||
|
||||
// keysyms::SunSys_Req => Key::PrintScreen,
|
||||
// The next couple of xkb (until SunStop) are already handled.
|
||||
// SunPrint_Screen..SunPageDown
|
||||
|
||||
// SunUndo..SunFront
|
||||
keysyms::SUN_Copy => Key::Copy,
|
||||
keysyms::SUN_Open => Key::Open,
|
||||
keysyms::SUN_Paste => Key::Paste,
|
||||
keysyms::SUN_Cut => Key::Cut,
|
||||
|
||||
// SunPowerSwitch
|
||||
keysyms::SUN_AudioLowerVolume => Key::AudioVolumeDown,
|
||||
keysyms::SUN_AudioMute => Key::AudioVolumeMute,
|
||||
keysyms::SUN_AudioRaiseVolume => Key::AudioVolumeUp,
|
||||
// SUN_VideoDegauss
|
||||
keysyms::SUN_VideoLowerBrightness => Key::BrightnessDown,
|
||||
keysyms::SUN_VideoRaiseBrightness => Key::BrightnessUp,
|
||||
// SunPowerSwitchShift
|
||||
//
|
||||
0 => Key::Unidentified(NativeKey::Unidentified),
|
||||
_ => Key::Unidentified(NativeKey::Xkb(keysym)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keysym_location(keysym: u32) -> KeyLocation {
|
||||
use xkbcommon_dl::keysyms;
|
||||
match keysym {
|
||||
keysyms::Shift_L
|
||||
| keysyms::Control_L
|
||||
| keysyms::Meta_L
|
||||
| keysyms::Alt_L
|
||||
| keysyms::Super_L
|
||||
| keysyms::Hyper_L => KeyLocation::Left,
|
||||
keysyms::Shift_R
|
||||
| keysyms::Control_R
|
||||
| keysyms::Meta_R
|
||||
| keysyms::Alt_R
|
||||
| keysyms::Super_R
|
||||
| keysyms::Hyper_R => KeyLocation::Right,
|
||||
keysyms::KP_0
|
||||
| keysyms::KP_1
|
||||
| keysyms::KP_2
|
||||
| keysyms::KP_3
|
||||
| keysyms::KP_4
|
||||
| keysyms::KP_5
|
||||
| keysyms::KP_6
|
||||
| keysyms::KP_7
|
||||
| keysyms::KP_8
|
||||
| keysyms::KP_9
|
||||
| keysyms::KP_Space
|
||||
| keysyms::KP_Tab
|
||||
| keysyms::KP_Enter
|
||||
| keysyms::KP_F1
|
||||
| keysyms::KP_F2
|
||||
| keysyms::KP_F3
|
||||
| keysyms::KP_F4
|
||||
| keysyms::KP_Home
|
||||
| keysyms::KP_Left
|
||||
| keysyms::KP_Up
|
||||
| keysyms::KP_Right
|
||||
| keysyms::KP_Down
|
||||
| keysyms::KP_Page_Up
|
||||
| keysyms::KP_Page_Down
|
||||
| keysyms::KP_End
|
||||
| keysyms::KP_Begin
|
||||
| keysyms::KP_Insert
|
||||
| keysyms::KP_Delete
|
||||
| keysyms::KP_Equal
|
||||
| keysyms::KP_Multiply
|
||||
| keysyms::KP_Add
|
||||
| keysyms::KP_Separator
|
||||
| keysyms::KP_Subtract
|
||||
| keysyms::KP_Decimal
|
||||
| keysyms::KP_Divide => KeyLocation::Numpad,
|
||||
_ => KeyLocation::Standard,
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod keymap;
|
||||
pub mod xkb_state;
|
||||
@@ -1,677 +0,0 @@
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use smol_str::SmolStr;
|
||||
use xkbcommon_dl::{
|
||||
self as ffi, xkb_state_component, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon,
|
||||
XkbCommonCompose,
|
||||
};
|
||||
#[cfg(feature = "wayland")]
|
||||
use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd};
|
||||
#[cfg(feature = "x11")]
|
||||
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
|
||||
|
||||
use crate::event::KeyEvent;
|
||||
use crate::platform_impl::common::keymap;
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
use crate::{
|
||||
event::ElementState,
|
||||
keyboard::{Key, KeyCode, KeyLocation},
|
||||
};
|
||||
|
||||
// TODO: Wire this up without using a static `AtomicBool`.
|
||||
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reset_dead_keys() {
|
||||
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
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 ffi::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KbdState {
|
||||
#[cfg(feature = "x11")]
|
||||
xcb_connection: *mut xcb_connection_t,
|
||||
xkb_context: *mut ffi::xkb_context,
|
||||
xkb_keymap: *mut ffi::xkb_keymap,
|
||||
xkb_state: *mut ffi::xkb_state,
|
||||
xkb_compose_table: *mut ffi::xkb_compose_table,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
xkb_compose_state_2: *mut ffi::xkb_compose_state,
|
||||
mods_state: ModifiersState,
|
||||
#[cfg(feature = "x11")]
|
||||
pub core_keyboard_id: i32,
|
||||
scratch_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl KbdState {
|
||||
pub fn update_modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
depressed_group: u32,
|
||||
latched_group: u32,
|
||||
locked_group: u32,
|
||||
) {
|
||||
if !self.ready() {
|
||||
return;
|
||||
}
|
||||
let mask = unsafe {
|
||||
(XKBH.xkb_state_update_mask)(
|
||||
self.xkb_state,
|
||||
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.mods_state.update_with(self.xkb_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_one_sym_raw(&mut self, keycode: u32) -> u32 {
|
||||
if !self.ready() {
|
||||
return 0;
|
||||
}
|
||||
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) }
|
||||
}
|
||||
|
||||
pub fn get_utf8_raw(&mut self, keycode: u32) -> Option<SmolStr> {
|
||||
if !self.ready() {
|
||||
return None;
|
||||
}
|
||||
let xkb_state = self.xkb_state;
|
||||
self.make_string_with({
|
||||
|ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(xkb_state, keycode, ptr, len) }
|
||||
})
|
||||
}
|
||||
|
||||
fn compose_feed_normal(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
|
||||
self.compose_feed(self.xkb_compose_state, keysym)
|
||||
}
|
||||
|
||||
fn compose_feed_2(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
|
||||
self.compose_feed(self.xkb_compose_state_2, keysym)
|
||||
}
|
||||
|
||||
fn compose_feed(
|
||||
&mut self,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
keysym: u32,
|
||||
) -> Option<ffi::xkb_compose_feed_result> {
|
||||
if !self.ready() || self.xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
|
||||
unsafe { self.init_compose() };
|
||||
}
|
||||
Some(unsafe { (XKBCH.xkb_compose_state_feed)(xkb_compose_state, keysym) })
|
||||
}
|
||||
|
||||
fn compose_status_normal(&mut self) -> Option<ffi::xkb_compose_status> {
|
||||
self.compose_status(self.xkb_compose_state)
|
||||
}
|
||||
|
||||
fn compose_status(
|
||||
&mut self,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
) -> Option<ffi::xkb_compose_status> {
|
||||
if !self.ready() || xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) })
|
||||
}
|
||||
|
||||
fn compose_get_utf8_normal(&mut self) -> Option<SmolStr> {
|
||||
self.compose_get_utf8(self.xkb_compose_state)
|
||||
}
|
||||
|
||||
fn compose_get_utf8_2(&mut self) -> Option<SmolStr> {
|
||||
self.compose_get_utf8(self.xkb_compose_state_2)
|
||||
}
|
||||
|
||||
fn compose_get_utf8(
|
||||
&mut self,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
) -> Option<SmolStr> {
|
||||
if !self.ready() || xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
self.make_string_with(|ptr, len| unsafe {
|
||||
(XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
|
||||
/// `xkb_state_key_get_utf8`.
|
||||
fn make_string_with<F>(&mut self, 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();
|
||||
self.scratch_buffer.clear();
|
||||
// The allocated buffer must include space for the null-terminator
|
||||
self.scratch_buffer.reserve(size + 1);
|
||||
unsafe {
|
||||
let written = f(
|
||||
self.scratch_buffer.as_mut_ptr().cast(),
|
||||
self.scratch_buffer.capacity(),
|
||||
);
|
||||
if usize::try_from(written).unwrap() != size {
|
||||
// This will likely never happen
|
||||
return None;
|
||||
}
|
||||
self.scratch_buffer.set_len(size);
|
||||
};
|
||||
byte_slice_to_smol_str(&self.scratch_buffer)
|
||||
}
|
||||
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
if ffi::xkbcommon_option().is_none() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let context =
|
||||
unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
|
||||
if context.is_null() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let mut me = Self {
|
||||
#[cfg(feature = "x11")]
|
||||
xcb_connection: ptr::null_mut(),
|
||||
xkb_context: context,
|
||||
xkb_keymap: ptr::null_mut(),
|
||||
xkb_state: ptr::null_mut(),
|
||||
xkb_compose_table: ptr::null_mut(),
|
||||
xkb_compose_state: ptr::null_mut(),
|
||||
xkb_compose_state_2: ptr::null_mut(),
|
||||
mods_state: ModifiersState::new(),
|
||||
#[cfg(feature = "x11")]
|
||||
core_keyboard_id: 0,
|
||||
scratch_buffer: Vec::new(),
|
||||
};
|
||||
|
||||
unsafe { me.init_compose() };
|
||||
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result<Self, Error> {
|
||||
let mut me = Self::new()?;
|
||||
me.xcb_connection = connection;
|
||||
|
||||
let result = unsafe {
|
||||
(XKBXH.xkb_x11_setup_xkb_extension)(
|
||||
connection,
|
||||
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(),
|
||||
)
|
||||
};
|
||||
assert_eq!(result, 1, "Failed to initialize libxkbcommon");
|
||||
|
||||
unsafe { me.init_with_x11_keymap() };
|
||||
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
unsafe fn init_compose(&mut 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 compose_table = (XKBCH.xkb_compose_table_new_from_locale)(
|
||||
self.xkb_context,
|
||||
locale.as_ptr(),
|
||||
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
|
||||
);
|
||||
|
||||
if compose_table.is_null() {
|
||||
// init of compose table failed, continue without compose
|
||||
return;
|
||||
}
|
||||
|
||||
let compose_state = (XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
);
|
||||
|
||||
if compose_state.is_null() {
|
||||
// init of compose state failed, continue without compose
|
||||
(XKBCH.xkb_compose_table_unref)(compose_table);
|
||||
return;
|
||||
}
|
||||
|
||||
let compose_state_2 = (XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
);
|
||||
|
||||
if compose_state_2.is_null() {
|
||||
// init of compose state failed, continue without compose
|
||||
(XKBCH.xkb_compose_table_unref)(compose_table);
|
||||
(XKBCH.xkb_compose_state_unref)(compose_state);
|
||||
return;
|
||||
}
|
||||
|
||||
self.xkb_compose_table = compose_table;
|
||||
self.xkb_compose_state = compose_state;
|
||||
self.xkb_compose_state_2 = compose_state_2;
|
||||
}
|
||||
|
||||
unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) {
|
||||
self.xkb_keymap = keymap;
|
||||
self.xkb_state = state;
|
||||
self.mods_state.update_with(state);
|
||||
}
|
||||
|
||||
unsafe fn de_init(&mut self) {
|
||||
(XKBH.xkb_state_unref)(self.xkb_state);
|
||||
self.xkb_state = ptr::null_mut();
|
||||
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
|
||||
self.xkb_keymap = ptr::null_mut();
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub unsafe fn init_with_x11_keymap(&mut self) {
|
||||
if !self.xkb_keymap.is_null() {
|
||||
self.de_init();
|
||||
}
|
||||
|
||||
// TODO: Support keyboards other than the "virtual core keyboard device".
|
||||
self.core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection);
|
||||
let keymap = (XKBXH.xkb_x11_keymap_new_from_device)(
|
||||
self.xkb_context,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
if keymap.is_null() {
|
||||
panic!("Failed to get keymap from X11 server.");
|
||||
}
|
||||
|
||||
let state = (XKBXH.xkb_x11_state_new_from_device)(
|
||||
keymap,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
);
|
||||
self.post_init(state, keymap);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) {
|
||||
if !self.xkb_keymap.is_null() {
|
||||
self.de_init();
|
||||
}
|
||||
|
||||
let map = MmapOptions::new()
|
||||
.len(size)
|
||||
.map_copy_read_only(&fd)
|
||||
.unwrap();
|
||||
|
||||
let keymap = (XKBH.xkb_keymap_new_from_string)(
|
||||
self.xkb_context,
|
||||
map.as_ptr() as *const _,
|
||||
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
|
||||
if keymap.is_null() {
|
||||
panic!("Received invalid keymap from compositor.");
|
||||
}
|
||||
|
||||
let state = (XKBH.xkb_state_new)(keymap);
|
||||
self.post_init(state, keymap);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
|
||||
unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ready(&self) -> bool {
|
||||
!self.xkb_state.is_null()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mods_state(&self) -> ModifiersState {
|
||||
self.mods_state
|
||||
}
|
||||
|
||||
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 = event.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 {
|
||||
key_without_modifiers,
|
||||
text_with_all_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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KbdState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.xkb_compose_state.is_null() {
|
||||
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state);
|
||||
}
|
||||
if !self.xkb_compose_state_2.is_null() {
|
||||
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state_2);
|
||||
}
|
||||
if !self.xkb_compose_table.is_null() {
|
||||
(XKBCH.xkb_compose_table_unref)(self.xkb_compose_table);
|
||||
}
|
||||
if !self.xkb_state.is_null() {
|
||||
(XKBH.xkb_state_unref)(self.xkb_state);
|
||||
}
|
||||
if !self.xkb_keymap.is_null() {
|
||||
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
|
||||
}
|
||||
(XKBH.xkb_context_unref)(self.xkb_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyEventResults<'a> {
|
||||
state: &'a mut KbdState,
|
||||
keycode: u32,
|
||||
keysym: u32,
|
||||
compose: Option<XkbCompose>,
|
||||
}
|
||||
|
||||
impl<'a> KeyEventResults<'a> {
|
||||
fn new(state: &'a mut KbdState, keycode: u32, compose: bool) -> Self {
|
||||
let keysym = state.get_one_sym_raw(keycode);
|
||||
|
||||
let compose = if compose {
|
||||
Some(match state.compose_feed_normal(keysym) {
|
||||
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => {
|
||||
// Unwrapping is safe here, as `compose_feed` returns `None` when composition is uninitialized.
|
||||
XkbCompose::Accepted(state.compose_status_normal().unwrap())
|
||||
}
|
||||
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored,
|
||||
None => XkbCompose::Uninitialized,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
KeyEventResults {
|
||||
state,
|
||||
keycode,
|
||||
keysym,
|
||||
compose,
|
||||
}
|
||||
}
|
||||
|
||||
fn keycode(&mut self) -> KeyCode {
|
||||
keymap::raw_keycode_to_keycode(self.keycode)
|
||||
}
|
||||
|
||||
pub fn key(&mut self) -> (Key, KeyLocation) {
|
||||
self.keysym_to_key(self.keysym)
|
||||
.unwrap_or_else(|(key, location)| match self.compose {
|
||||
Some(XkbCompose::Accepted(ffi::xkb_compose_status::XKB_COMPOSE_COMPOSING)) => {
|
||||
// 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.
|
||||
self.state.compose_feed_2(self.keysym);
|
||||
match self.state.compose_feed_2(self.keysym) {
|
||||
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_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`.
|
||||
Key::Dead(
|
||||
self.state
|
||||
.compose_get_utf8_2()
|
||||
.map(|s| s.chars().next().unwrap()),
|
||||
),
|
||||
location,
|
||||
),
|
||||
_ => (key, location),
|
||||
}
|
||||
}
|
||||
_ => (
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
|
||||
.map(Key::Character)
|
||||
.unwrap_or(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 mut keysyms = ptr::null();
|
||||
let keysym_count = unsafe {
|
||||
let layout = (XKBH.xkb_state_key_get_layout)(self.state.xkb_state, self.keycode);
|
||||
(XKBH.xkb_keymap_key_get_syms_by_level)(
|
||||
self.state.xkb_keymap,
|
||||
self.keycode,
|
||||
layout,
|
||||
// NOTE: The level should be zero to ignore modifiers.
|
||||
0,
|
||||
&mut keysyms,
|
||||
)
|
||||
};
|
||||
let keysym = if keysym_count == 1 {
|
||||
unsafe { *keysyms }
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.keysym_to_key(keysym)
|
||||
.unwrap_or_else(|(key, location)| {
|
||||
(
|
||||
self.state
|
||||
.keysym_to_utf8_raw(keysym)
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key),
|
||||
location,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
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(_)) {
|
||||
Err((key, location))
|
||||
} else {
|
||||
Ok((key, location))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&mut self) -> Option<SmolStr> {
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
|
||||
}
|
||||
|
||||
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
|
||||
// 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.
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.state.get_utf8_raw(self.keycode))
|
||||
}
|
||||
|
||||
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
|
||||
if let Some(compose) = &self.compose {
|
||||
match compose {
|
||||
XkbCompose::Accepted(status) => match status {
|
||||
ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => {
|
||||
Ok(self.state.compose_get_utf8_normal())
|
||||
}
|
||||
ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
|
||||
_ => Ok(None),
|
||||
},
|
||||
XkbCompose::Ignored | XkbCompose::Uninitialized => Err(()),
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 ModifiersState {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn update_with(&mut self, state: *mut ffi::xkb_state) {
|
||||
let mod_name_is_active = |mod_name: &[u8]| unsafe {
|
||||
(XKBH.xkb_state_mod_name_is_active)(
|
||||
state,
|
||||
mod_name.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
|
||||
) > 0
|
||||
};
|
||||
self.ctrl = mod_name_is_active(ffi::XKB_MOD_NAME_CTRL);
|
||||
self.alt = mod_name_is_active(ffi::XKB_MOD_NAME_ALT);
|
||||
self.shift = mod_name_is_active(ffi::XKB_MOD_NAME_SHIFT);
|
||||
self.caps_lock = mod_name_is_active(ffi::XKB_MOD_NAME_CAPS);
|
||||
self.logo = mod_name_is_active(ffi::XKB_MOD_NAME_LOGO);
|
||||
self.num_lock = mod_name_is_active(ffi::XKB_MOD_NAME_NUM);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// libxkbcommon is not available
|
||||
XKBNotFound,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum XkbCompose {
|
||||
Accepted(ffi::xkb_compose_status),
|
||||
Ignored,
|
||||
Uninitialized,
|
||||
}
|
||||
|
||||
// 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| {
|
||||
warn!(
|
||||
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
|
||||
bytes
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
@@ -18,7 +18,6 @@ use std::{
|
||||
#[cfg(x11_platform)]
|
||||
use once_cell::sync::Lazy;
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
use smol_str::SmolStr;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub use self::x11::XNotSupported;
|
||||
@@ -29,11 +28,11 @@ use crate::platform::x11::XlibErrorHook;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::{Event, KeyEvent},
|
||||
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
event::Event,
|
||||
event_loop::{
|
||||
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
||||
},
|
||||
icon::Icon,
|
||||
keyboard::{Key, KeyCode},
|
||||
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
WindowAttributes, WindowButtons, WindowLevel,
|
||||
@@ -43,7 +42,6 @@ use crate::{
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
pub(self) use crate::platform_impl::Fullscreen;
|
||||
|
||||
pub mod common;
|
||||
#[cfg(wayland_platform)]
|
||||
pub mod wayland;
|
||||
#[cfg(x11_platform)]
|
||||
@@ -512,13 +510,8 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_dead_keys(&self) {
|
||||
common::xkb_state::reset_dead_keys()
|
||||
pub fn set_ime_position(&self, position: Position) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -631,37 +624,6 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeyEventExtra {
|
||||
pub key_without_modifiers: Key,
|
||||
pub text_with_all_modifiers: Option<SmolStr>,
|
||||
}
|
||||
|
||||
impl KeyEventExtModifierSupplement for KeyEvent {
|
||||
#[inline]
|
||||
fn text_with_all_modifiers(&self) -> Option<&str> {
|
||||
self.platform_specific
|
||||
.text_with_all_modifiers
|
||||
.as_ref()
|
||||
.map(|s| s.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn key_without_modifiers(&self) -> Key {
|
||||
self.platform_specific.key_without_modifiers.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyCodeExtScancode for KeyCode {
|
||||
fn from_scancode(scancode: u32) -> KeyCode {
|
||||
common::keymap::scancode_to_keycode(scancode)
|
||||
}
|
||||
|
||||
fn to_scancode(self) -> Option<u32> {
|
||||
common::keymap::keycode_to_scancode(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Hooks for X11 errors.
|
||||
#[cfg(x11_platform)]
|
||||
pub(crate) static mut XLIB_ERROR_HOOKS: Lazy<Mutex<Vec<XlibErrorHook>>> =
|
||||
@@ -892,12 +854,12 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
|
||||
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
|
||||
match *self {
|
||||
#[cfg(wayland_platform)]
|
||||
EventLoopWindowTarget::Wayland(_) => (),
|
||||
#[cfg(x11_platform)]
|
||||
EventLoopWindowTarget::X(ref evlp) => evlp.set_listen_device_events(_allowed),
|
||||
EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
183
src/platform_impl/linux/wayland/env.rs
Normal file
183
src/platform_impl/linux/wayland/env.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
//! SCTK environment setup.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
|
||||
use sctk::reexports::client::protocol::wl_shell::WlShell;
|
||||
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
|
||||
use sctk::reexports::client::{Attached, DispatchData};
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
||||
use sctk::reexports::protocols::viewporter::client::wp_viewporter::WpViewporter;
|
||||
|
||||
use sctk::environment::{Environment, SimpleGlobal};
|
||||
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
|
||||
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
|
||||
use sctk::shell::{Shell, ShellHandler, ShellHandling};
|
||||
use sctk::shm::ShmHandler;
|
||||
|
||||
use crate::platform_impl::wayland::protocols::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
||||
|
||||
/// Set of extra features that are supported by the compositor.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WindowingFeatures {
|
||||
pointer_constraints: bool,
|
||||
xdg_activation: bool,
|
||||
}
|
||||
|
||||
impl WindowingFeatures {
|
||||
/// Create `WindowingFeatures` based on the presented interfaces.
|
||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
|
||||
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
|
||||
Self {
|
||||
pointer_constraints,
|
||||
xdg_activation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pointer_constraints(&self) -> bool {
|
||||
self.pointer_constraints
|
||||
}
|
||||
|
||||
pub fn xdg_activation(&self) -> bool {
|
||||
self.xdg_activation
|
||||
}
|
||||
}
|
||||
|
||||
sctk::environment!(WinitEnv,
|
||||
singles = [
|
||||
WlShm => shm,
|
||||
WlCompositor => compositor,
|
||||
WlSubcompositor => subcompositor,
|
||||
WlShell => shell,
|
||||
XdgWmBase => shell,
|
||||
ZxdgShellV6 => shell,
|
||||
ZxdgDecorationManagerV1 => decoration_manager,
|
||||
ZwpRelativePointerManagerV1 => relative_pointer_manager,
|
||||
ZwpPointerConstraintsV1 => pointer_constraints,
|
||||
ZwpTextInputManagerV3 => text_input_manager,
|
||||
XdgActivationV1 => xdg_activation,
|
||||
WpFractionalScaleManagerV1 => fractional_scale_manager,
|
||||
WpViewporter => viewporter,
|
||||
],
|
||||
multis = [
|
||||
WlSeat => seats,
|
||||
WlOutput => outputs,
|
||||
]
|
||||
);
|
||||
|
||||
/// The environment that we utilize.
|
||||
pub struct WinitEnv {
|
||||
seats: SeatHandler,
|
||||
|
||||
outputs: OutputHandler,
|
||||
|
||||
shm: ShmHandler,
|
||||
|
||||
compositor: SimpleGlobal<WlCompositor>,
|
||||
|
||||
subcompositor: SimpleGlobal<WlSubcompositor>,
|
||||
|
||||
shell: ShellHandler,
|
||||
|
||||
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
|
||||
|
||||
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
|
||||
|
||||
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
|
||||
|
||||
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
|
||||
|
||||
xdg_activation: SimpleGlobal<XdgActivationV1>,
|
||||
|
||||
fractional_scale_manager: SimpleGlobal<WpFractionalScaleManagerV1>,
|
||||
|
||||
viewporter: SimpleGlobal<WpViewporter>,
|
||||
}
|
||||
|
||||
impl WinitEnv {
|
||||
pub fn new() -> Self {
|
||||
// Output tracking for available_monitors, etc.
|
||||
let outputs = OutputHandler::new();
|
||||
|
||||
// Keyboard/Pointer/Touch input.
|
||||
let seats = SeatHandler::new();
|
||||
|
||||
// Essential globals.
|
||||
let shm = ShmHandler::new();
|
||||
let compositor = SimpleGlobal::new();
|
||||
let subcompositor = SimpleGlobal::new();
|
||||
|
||||
// Gracefully handle shell picking, since SCTK automatically supports multiple
|
||||
// backends.
|
||||
let shell = ShellHandler::new();
|
||||
|
||||
// Server side decorations.
|
||||
let decoration_manager = SimpleGlobal::new();
|
||||
|
||||
// Device events for pointer.
|
||||
let relative_pointer_manager = SimpleGlobal::new();
|
||||
|
||||
// Pointer grab functionality.
|
||||
let pointer_constraints = SimpleGlobal::new();
|
||||
|
||||
// IME handling.
|
||||
let text_input_manager = SimpleGlobal::new();
|
||||
|
||||
// Surface activation.
|
||||
let xdg_activation = SimpleGlobal::new();
|
||||
|
||||
// Fractional surface scaling.
|
||||
let fractional_scale_manager = SimpleGlobal::new();
|
||||
|
||||
// Surface resizing (used for fractional scaling).
|
||||
let viewporter = SimpleGlobal::new();
|
||||
|
||||
Self {
|
||||
seats,
|
||||
outputs,
|
||||
shm,
|
||||
compositor,
|
||||
subcompositor,
|
||||
shell,
|
||||
decoration_manager,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
xdg_activation,
|
||||
fractional_scale_manager,
|
||||
viewporter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellHandling for WinitEnv {
|
||||
fn get_shell(&self) -> Option<Shell> {
|
||||
self.shell.get_shell()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandling for WinitEnv {
|
||||
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> SeatListener {
|
||||
self.seats.listen(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputHandling for WinitEnv {
|
||||
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> OutputStatusListener {
|
||||
self.outputs.listen(f)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
//! The event-loop routines.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::io::Result as IOResult;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::process;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
use sctk::reexports::client::globals;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
|
||||
use sctk::WaylandSource;
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
use crate::event::{Event, StartCause, WindowEvent};
|
||||
@@ -22,72 +25,135 @@ use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindo
|
||||
use crate::platform_impl::platform::sticky_exit_callback;
|
||||
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
|
||||
|
||||
mod proxy;
|
||||
pub mod sink;
|
||||
|
||||
pub use proxy::EventLoopProxy;
|
||||
use sink::EventSink;
|
||||
|
||||
use super::state::{WindowCompositorUpdate, WinitState};
|
||||
use super::env::{WindowingFeatures, WinitEnv};
|
||||
use super::output::OutputManager;
|
||||
use super::seat::SeatManager;
|
||||
use super::window::shim::{self, WindowCompositorUpdate, WindowUserRequest};
|
||||
use super::{DeviceId, WindowId};
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
mod proxy;
|
||||
mod sink;
|
||||
mod state;
|
||||
|
||||
pub use proxy::EventLoopProxy;
|
||||
pub use sink::EventSink;
|
||||
pub use state::WinitState;
|
||||
|
||||
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
/// Wayland display.
|
||||
pub display: Display,
|
||||
|
||||
/// Environment to handle object creation, etc.
|
||||
pub env: Environment<WinitEnv>,
|
||||
|
||||
/// Event loop handle.
|
||||
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
|
||||
|
||||
/// Output manager.
|
||||
pub output_manager: OutputManager,
|
||||
|
||||
/// State that we share across callbacks.
|
||||
pub state: RefCell<WinitState>,
|
||||
|
||||
/// Dispatcher of Wayland events.
|
||||
pub wayland_dispatcher: WinitDispatcher,
|
||||
|
||||
/// A proxy to wake up event loop.
|
||||
pub event_loop_awakener: calloop::ping::Ping,
|
||||
|
||||
/// The available windowing features.
|
||||
pub windowing_features: WindowingFeatures,
|
||||
|
||||
/// Theme manager to manage cursors.
|
||||
///
|
||||
/// It's being shared between all windows to avoid loading
|
||||
/// multiple similar themes.
|
||||
pub theme_manager: ThemeManager,
|
||||
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
display_handle.display = self.display.get_display_ptr() as *mut _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
}
|
||||
}
|
||||
|
||||
/// The Wayland event loop.
|
||||
pub struct EventLoop<T: 'static> {
|
||||
/// Dispatcher of Wayland events.
|
||||
pub wayland_dispatcher: WinitDispatcher,
|
||||
|
||||
/// Event loop.
|
||||
event_loop: calloop::EventLoop<'static, WinitState>,
|
||||
|
||||
/// Wayland display.
|
||||
display: Display,
|
||||
|
||||
/// Pending user events.
|
||||
pending_user_events: Rc<RefCell<Vec<T>>>,
|
||||
|
||||
/// Sender of user events.
|
||||
user_events_sender: calloop::channel::Sender<T>,
|
||||
|
||||
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
|
||||
// we don't really want, since it'll break public API by a lot.
|
||||
/// Pending events from the user.
|
||||
pending_user_events: Rc<RefCell<Vec<T>>>,
|
||||
|
||||
/// The Wayland dispatcher to has raw access to the queue when needed, such as
|
||||
/// when creating a new window.
|
||||
wayland_dispatcher: WaylandDispatcher,
|
||||
|
||||
/// Connection to the wayland server.
|
||||
connection: Connection,
|
||||
|
||||
/// Event loop window target.
|
||||
/// Window target.
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
|
||||
// XXX drop after everything else, just to be safe.
|
||||
/// Calloop's event loop.
|
||||
event_loop: calloop::EventLoop<'static, WinitState>,
|
||||
/// Output manager.
|
||||
_seat_manager: SeatManager,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
let connection = Connection::connect_to_env()?;
|
||||
// Connect to wayland server and setup event queue.
|
||||
let display = Display::connect_to_env()?;
|
||||
let mut event_queue = display.create_event_queue();
|
||||
let display_proxy = display.attach(event_queue.token());
|
||||
|
||||
let (globals, mut event_queue) = globals::registry_queue_init(&connection)?;
|
||||
let queue_handle = event_queue.handle();
|
||||
// Setup environment.
|
||||
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
|
||||
|
||||
let event_loop = calloop::EventLoop::<WinitState>::try_new()?;
|
||||
// Create event loop.
|
||||
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
|
||||
// Build windowing features.
|
||||
let windowing_features = WindowingFeatures::new(&env);
|
||||
|
||||
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
|
||||
// Create a theme manager.
|
||||
let compositor = env.require_global::<WlCompositor>();
|
||||
let shm = env.require_global::<WlShm>();
|
||||
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
|
||||
|
||||
// NOTE: do a roundtrip after binding the globals to prevent potential
|
||||
// races with the server.
|
||||
event_queue.roundtrip(&mut winit_state)?;
|
||||
// Setup theme seat and output managers.
|
||||
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
|
||||
let output_manager = OutputManager::new(&env);
|
||||
|
||||
// Register Wayland source.
|
||||
let wayland_source = WaylandSource::new(event_queue)?;
|
||||
// A source of events that we plug into our event loop.
|
||||
let wayland_source = WaylandSource::new(event_queue);
|
||||
let wayland_dispatcher =
|
||||
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
|
||||
queue.dispatch_pending(winit_state)
|
||||
queue.dispatch_pending(winit_state, |event, object, _| {
|
||||
panic!(
|
||||
"[calloop] Encountered an orphan event: {}@{} : {}",
|
||||
event.interface,
|
||||
object.as_ref().id(),
|
||||
event.name
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
event_loop
|
||||
let _wayland_source_dispatcher = event_loop
|
||||
.handle()
|
||||
.register_dispatcher(wayland_dispatcher.clone())?;
|
||||
|
||||
// Setup the user proxy.
|
||||
// A source of user events.
|
||||
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
|
||||
let pending_user_events_clone = pending_user_events.clone();
|
||||
let (user_events_sender, user_events_channel) = calloop::channel::channel();
|
||||
|
||||
// User events channel.
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(user_events_channel, move |event, _, _| {
|
||||
@@ -98,30 +164,52 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
// An event's loop awakener to wake up for window events from winit's windows.
|
||||
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
|
||||
|
||||
// Handler of window requests.
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(event_loop_awakener_source, move |_, _, _| {
|
||||
// No extra handling is required, we just need to wake-up.
|
||||
.insert_source(event_loop_awakener_source, move |_, _, state| {
|
||||
// Drain events here as well to account for application doing batch event processing
|
||||
// on RedrawEventsCleared.
|
||||
shim::handle_window_requests(state);
|
||||
})?;
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
connection: connection.clone(),
|
||||
wayland_dispatcher: wayland_dispatcher.clone(),
|
||||
let event_loop_handle = event_loop.handle();
|
||||
let window_map = HashMap::new();
|
||||
let event_sink = EventSink::new();
|
||||
let window_user_requests = HashMap::new();
|
||||
let window_compositor_updates = HashMap::new();
|
||||
|
||||
// Create event loop window target.
|
||||
let event_loop_window_target = EventLoopWindowTarget {
|
||||
display: display.clone(),
|
||||
env,
|
||||
state: RefCell::new(WinitState {
|
||||
window_map,
|
||||
event_sink,
|
||||
window_user_requests,
|
||||
window_compositor_updates,
|
||||
}),
|
||||
event_loop_handle,
|
||||
output_manager,
|
||||
event_loop_awakener,
|
||||
queue_handle,
|
||||
state: RefCell::new(winit_state),
|
||||
_marker: PhantomData,
|
||||
wayland_dispatcher: wayland_dispatcher.clone(),
|
||||
windowing_features,
|
||||
theme_manager,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
// Create event loop itself.
|
||||
let event_loop = Self {
|
||||
connection,
|
||||
wayland_dispatcher,
|
||||
user_events_sender,
|
||||
pending_user_events,
|
||||
event_loop,
|
||||
display,
|
||||
pending_user_events,
|
||||
wayland_dispatcher,
|
||||
_seat_manager: seat_manager,
|
||||
user_events_sender,
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: PlatformEventLoopWindowTarget::Wayland(window_target),
|
||||
_marker: PhantomData,
|
||||
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -141,11 +229,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
let mut control_flow = ControlFlow::Poll;
|
||||
|
||||
// XXX preallocate certian structures to avoid allocating on each loop iteration.
|
||||
let mut window_ids = Vec::<WindowId>::new();
|
||||
let mut compositor_updates = Vec::<WindowCompositorUpdate>::new();
|
||||
let mut buffer_sink = EventSink::new();
|
||||
let pending_user_events = self.pending_user_events.clone();
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Init),
|
||||
@@ -153,19 +237,23 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut control_flow,
|
||||
);
|
||||
|
||||
// XXX For consistency all platforms must emit a 'Resumed' event even though Wayland
|
||||
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
|
||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
||||
callback(Event::Resumed, &self.window_target, &mut control_flow);
|
||||
|
||||
// XXX We break on errors from dispatches, since if we've got protocol error
|
||||
let mut window_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new();
|
||||
let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = Vec::new();
|
||||
let mut event_sink_back_buffer = Vec::new();
|
||||
|
||||
// NOTE We break on errors from dispatches, since if we've got protocol error
|
||||
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
|
||||
// really an option. Instead we inform that the event loop got destroyed. We may
|
||||
// communicate an error that something was terminated, but winit doesn't provide us
|
||||
// with an API to do that via some event.
|
||||
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
|
||||
let exit_code = loop {
|
||||
// Flush the connection.
|
||||
let _ = self.connection.flush();
|
||||
// Send pending events to the server.
|
||||
let _ = self.display.flush();
|
||||
|
||||
// During the run of the user callback, some other code monitoring and reading the
|
||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
||||
@@ -185,12 +273,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match queue.dispatch_pending(state) {
|
||||
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
|
||||
Ok(dispatched) => dispatched > 0,
|
||||
Err(error) => {
|
||||
error!("Error dispatching wayland queue: {}", error);
|
||||
break 1;
|
||||
}
|
||||
Err(error) => break error.raw_os_error().unwrap_or(1),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,7 +283,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
ControlFlow::ExitWithCode(code) => break code,
|
||||
ControlFlow::Poll => {
|
||||
// Non-blocking dispatch.
|
||||
let timeout = Duration::ZERO;
|
||||
let timeout = Duration::from_millis(0);
|
||||
if let Err(error) = self.loop_dispatch(Some(timeout)) {
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
}
|
||||
@@ -211,7 +296,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
let timeout = if instant_wakeup {
|
||||
Some(Duration::ZERO)
|
||||
Some(Duration::from_millis(0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -236,7 +321,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
let duration = if deadline > start && !instant_wakeup {
|
||||
deadline - start
|
||||
} else {
|
||||
Duration::ZERO
|
||||
Duration::from_millis(0)
|
||||
};
|
||||
|
||||
if let Err(error) = self.loop_dispatch(Some(duration)) {
|
||||
@@ -269,7 +354,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
// Handle pending user events. We don't need back buffer, since we can't dispatch
|
||||
// user events indirectly via callback to the user.
|
||||
for user_event in self.pending_user_events.borrow_mut().drain(..) {
|
||||
for user_event in pending_user_events.borrow_mut().drain(..) {
|
||||
sticky_exit_callback(
|
||||
Event::UserEvent(user_event),
|
||||
&self.window_target,
|
||||
@@ -278,30 +363,35 @@ impl<T: 'static> EventLoop<T> {
|
||||
);
|
||||
}
|
||||
|
||||
// Drain the pending compositor updates.
|
||||
// Process 'new' pending updates from compositor.
|
||||
self.with_state(|state| {
|
||||
compositor_updates.append(&mut state.window_compositor_updates)
|
||||
window_compositor_updates.clear();
|
||||
window_compositor_updates.extend(
|
||||
state
|
||||
.window_compositor_updates
|
||||
.iter_mut()
|
||||
.map(|(wid, window_update)| (*wid, mem::take(window_update))),
|
||||
);
|
||||
});
|
||||
|
||||
for mut compositor_update in compositor_updates.drain(..) {
|
||||
let window_id = compositor_update.window_id;
|
||||
if let Some(scale_factor) = compositor_update.scale_factor {
|
||||
for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() {
|
||||
if let Some(scale_factor) = window_compositor_update.scale_factor {
|
||||
let mut physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
let window_handle = state.window_map.get(window_id).unwrap();
|
||||
*window_handle.scale_factor.lock().unwrap() = scale_factor;
|
||||
|
||||
let mut size = window_handle.size.lock().unwrap();
|
||||
|
||||
// Update the new logical size if it was changed.
|
||||
let window_size = window_compositor_update.size.unwrap_or(*size);
|
||||
*size = window_size;
|
||||
|
||||
// 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.
|
||||
let old_physical_size = physical_size;
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut physical_size,
|
||||
@@ -312,58 +402,83 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// We don't update size on a window handle since we'll do that later
|
||||
// when handling size update.
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
|
||||
// Resize the window when user altered the size.
|
||||
if old_physical_size != physical_size {
|
||||
self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
window.resize(new_logical_size);
|
||||
});
|
||||
}
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.size = Some(new_logical_size);
|
||||
window_compositor_update.size = Some(new_logical_size);
|
||||
}
|
||||
|
||||
if let Some(size) = compositor_update.size.take() {
|
||||
if let Some(size) = window_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 window_handle = state.window_map.get_mut(window_id).unwrap();
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
if let Some(fs_state) = window_handle.fractional_scaling_state.as_ref() {
|
||||
// If we have a viewport then we support fractional scaling. As per the
|
||||
// protocol, we have to set the viewport size of the size prior scaling.
|
||||
fs_state
|
||||
.viewport
|
||||
.set_destination(size.width as _, size.height as _);
|
||||
}
|
||||
|
||||
// TODO could probably bring back size reporting optimization.
|
||||
let mut window_size = window_handle.size.lock().unwrap();
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
// Always issue resize event on scale factor change.
|
||||
let physical_size = if window_compositor_update.scale_factor.is_none()
|
||||
&& *window_size == size
|
||||
{
|
||||
// The size hasn't changed, don't inform downstream about that.
|
||||
None
|
||||
} else {
|
||||
*window_size = size;
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
Some(physical_size)
|
||||
};
|
||||
|
||||
// We still perform all of those resize related logic even if the size
|
||||
// hasn't changed, since GNOME relies on `set_geometry` calls after
|
||||
// configures.
|
||||
window_handle.window.resize(size.width, size.height);
|
||||
window_handle.window.refresh();
|
||||
|
||||
// Update the opaque region.
|
||||
window_handle.set_transparent(window_handle.transparent.get());
|
||||
|
||||
// Mark that refresh isn't required, since we've done it right now.
|
||||
state
|
||||
.window_requests
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.window_user_requests
|
||||
.get_mut(window_id)
|
||||
.unwrap()
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
.refresh_frame = false;
|
||||
|
||||
// Queue redraw requested.
|
||||
state
|
||||
.window_user_requests
|
||||
.get_mut(window_id)
|
||||
.unwrap()
|
||||
.redraw_requested = true;
|
||||
|
||||
physical_size
|
||||
});
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
if let Some(physical_size) = physical_size {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if compositor_update.close_window {
|
||||
// If the close is requested, send it here.
|
||||
if window_compositor_update.close_window {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
window_id: crate::window::WindowId(*window_id),
|
||||
event: WindowEvent::CloseRequested,
|
||||
},
|
||||
&self.window_target,
|
||||
@@ -373,20 +488,18 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Push the events directly from the window.
|
||||
// The purpose of the back buffer and that swap is to not hold borrow_mut when
|
||||
// we're doing callback to the user, since we can double borrow if the user decides
|
||||
// to create a window in one of those callbacks.
|
||||
self.with_state(|state| {
|
||||
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
|
||||
std::mem::swap(
|
||||
&mut event_sink_back_buffer,
|
||||
&mut state.event_sink.window_events,
|
||||
)
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
}
|
||||
|
||||
// Handle non-synthetic events.
|
||||
self.with_state(|state| {
|
||||
buffer_sink.append(&mut state.events_sink);
|
||||
});
|
||||
for event in buffer_sink.drain() {
|
||||
// Handle pending window events.
|
||||
for event in event_sink_back_buffer.drain(..) {
|
||||
let event = event.map_nonuser_event().unwrap();
|
||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
||||
}
|
||||
@@ -399,41 +512,42 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// Collect the window ids
|
||||
// Apply user requests, so every event required resize and latter surface commit will
|
||||
// be applied right before drawing. This will also ensure that every `RedrawRequested`
|
||||
// event will be delivered in time.
|
||||
self.with_state(|state| {
|
||||
window_ids.extend(state.window_requests.get_mut().keys());
|
||||
shim::handle_window_requests(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));
|
||||
false
|
||||
} else {
|
||||
let mut redraw_requested = window_requests
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
// Process 'new' pending updates from compositor.
|
||||
self.with_state(|state| {
|
||||
window_user_requests.clear();
|
||||
window_user_requests.extend(
|
||||
state
|
||||
.window_user_requests
|
||||
.iter_mut()
|
||||
.map(|(wid, window_request)| (*wid, mem::take(window_request))),
|
||||
);
|
||||
});
|
||||
|
||||
// Redraw the frames while at it.
|
||||
redraw_requested |= state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.refresh_frame();
|
||||
// Handle RedrawRequested events.
|
||||
for (window_id, mut window_request) in window_user_requests.iter() {
|
||||
// Handle refresh of the frame.
|
||||
if window_request.refresh_frame {
|
||||
self.with_state(|state| {
|
||||
let window_handle = state.window_map.get_mut(window_id).unwrap();
|
||||
window_handle.window.refresh();
|
||||
});
|
||||
|
||||
redraw_requested
|
||||
}
|
||||
});
|
||||
// In general refreshing the frame requires surface commit, those force user
|
||||
// to redraw.
|
||||
window_request.redraw_requested = true;
|
||||
}
|
||||
|
||||
if request_redraw {
|
||||
// Handle redraw request.
|
||||
if window_request.redraw_requested {
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(crate::window::WindowId(window_id)),
|
||||
Event::RedrawRequested(crate::window::WindowId(*window_id)),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
@@ -464,55 +578,26 @@ impl<T: 'static> EventLoop<T> {
|
||||
&self.window_target
|
||||
}
|
||||
|
||||
fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U {
|
||||
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
||||
#[cfg(x11_platform)]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
callback(state)
|
||||
f(state)
|
||||
}
|
||||
|
||||
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
|
||||
let state = match &mut self.window_target.p {
|
||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
||||
#[cfg(feature = "x11")]
|
||||
#[cfg(x11_platform)]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.event_loop.dispatch(timeout, state).map_err(|error| {
|
||||
error!("Error dispatching event loop: {}", error);
|
||||
error.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
/// The event loop wakeup source.
|
||||
pub event_loop_awakener: calloop::ping::Ping,
|
||||
|
||||
/// The main queue used by the event loop.
|
||||
pub queue_handle: QueueHandle<WinitState>,
|
||||
|
||||
// TODO remove that RefCell once we can pass `&mut` in `Window::new`.
|
||||
/// Winit state.
|
||||
pub state: RefCell<WinitState>,
|
||||
|
||||
/// Dispatcher of Wayland events.
|
||||
pub wayland_dispatcher: WaylandDispatcher,
|
||||
|
||||
/// Connection to the wayland server.
|
||||
pub connection: Connection,
|
||||
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut display_handle = WaylandDisplayHandle::empty();
|
||||
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
|
||||
RawDisplayHandle::Wayland(display_handle)
|
||||
self.event_loop
|
||||
.dispatch(timeout, state)
|
||||
.map_err(|error| error.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! An event loop's sink to deliver events from the Wayland event callbacks.
|
||||
|
||||
use std::vec::Drain;
|
||||
|
||||
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
|
||||
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
@@ -21,7 +19,6 @@ impl EventSink {
|
||||
}
|
||||
|
||||
/// Add new device event to a queue.
|
||||
#[inline]
|
||||
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
|
||||
self.window_events.push(Event::DeviceEvent {
|
||||
event,
|
||||
@@ -30,21 +27,10 @@ impl EventSink {
|
||||
}
|
||||
|
||||
/// Add new window event to a queue.
|
||||
#[inline]
|
||||
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
|
||||
self.window_events.push(Event::WindowEvent {
|
||||
event,
|
||||
window_id: RootWindowId(window_id),
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn append(&mut self, other: &mut Self) {
|
||||
self.window_events.append(&mut other.window_events);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> {
|
||||
self.window_events.drain(..)
|
||||
}
|
||||
}
|
||||
|
||||
31
src/platform_impl/linux/wayland/event_loop/state.rs
Normal file
31
src/platform_impl/linux/wayland/event_loop/state.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! A state that we pass around in a dispatch.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::EventSink;
|
||||
use crate::platform_impl::wayland::window::shim::{
|
||||
WindowCompositorUpdate, WindowHandle, WindowUserRequest,
|
||||
};
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
/// Wrapper to carry winit's state.
|
||||
pub struct WinitState {
|
||||
/// A sink for window and device events that is being filled during dispatching
|
||||
/// event loop and forwarded downstream afterwards.
|
||||
pub event_sink: EventSink,
|
||||
|
||||
/// Window updates comming from the user requests. Those are separatelly dispatched right after
|
||||
/// `MainEventsCleared`.
|
||||
pub window_user_requests: HashMap<WindowId, WindowUserRequest>,
|
||||
|
||||
/// Window updates, which are coming from SCTK or the compositor, which require
|
||||
/// calling back to the winit's downstream. They are handled right in the event loop,
|
||||
/// unlike the ones coming from buffers on the `WindowHandle`'s.
|
||||
pub window_compositor_updates: HashMap<WindowId, WindowCompositorUpdate>,
|
||||
|
||||
/// Window map containing all SCTK windows. Since those windows aren't allowed
|
||||
/// to be sent to other threads, they live on the event loop's thread
|
||||
/// and requests from winit's windows are being forwarded to them either via
|
||||
/// `WindowUpdate` or buffer on the associated with it `WindowHandle`.
|
||||
pub window_map: HashMap<WindowId, WindowHandle>,
|
||||
}
|
||||
@@ -1,23 +1,19 @@
|
||||
#![cfg(wayland_platform)]
|
||||
|
||||
//! Winit's Wayland backend.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
|
||||
pub use crate::platform_impl::platform::WindowId;
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
pub use window::Window;
|
||||
|
||||
mod env;
|
||||
mod event_loop;
|
||||
mod output;
|
||||
mod protocols;
|
||||
mod seat;
|
||||
mod state;
|
||||
mod types;
|
||||
mod window;
|
||||
|
||||
/// Dummy device id, since Wayland doesn't have device events.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
@@ -27,8 +23,7 @@ impl DeviceId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the WindowId out of the surface.
|
||||
#[inline]
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.id().as_ptr() as u64)
|
||||
WindowId(surface.as_ref().c_ptr() as u64)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,98 @@
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Proxy;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::output::OutputData;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::output::OutputStatusListener;
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::platform_impl::platform::{
|
||||
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
|
||||
};
|
||||
|
||||
use super::env::WinitEnv;
|
||||
use super::event_loop::EventLoopWindowTarget;
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
|
||||
self.state
|
||||
.borrow()
|
||||
.output_state
|
||||
.outputs()
|
||||
.map(MonitorHandle::new)
|
||||
.collect()
|
||||
/// Output manager.
|
||||
pub struct OutputManager {
|
||||
/// A handle that actually performs all operations on outputs.
|
||||
handle: OutputManagerHandle,
|
||||
|
||||
_output_listener: OutputStatusListener,
|
||||
}
|
||||
|
||||
impl OutputManager {
|
||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
||||
let handle = OutputManagerHandle::new();
|
||||
|
||||
// Handle existing outputs.
|
||||
for output in env.get_all_outputs() {
|
||||
match sctk::output::with_output_info(&output, |info| info.obsolete) {
|
||||
Some(false) => (),
|
||||
// The output is obsolete or we've failed to access its data, skipping.
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// The output is present and unusable, add it to the output manager manager.
|
||||
handle.add_output(output);
|
||||
}
|
||||
|
||||
let handle_for_listener = handle.clone();
|
||||
|
||||
let output_listener = env.listen_for_outputs(move |output, info, _| {
|
||||
if info.obsolete {
|
||||
handle_for_listener.remove_output(output)
|
||||
} else {
|
||||
handle_for_listener.add_output(output)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
handle,
|
||||
_output_listener: output_listener,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
pub fn handle(&self) -> OutputManagerHandle {
|
||||
self.handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to output manager.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputManagerHandle {
|
||||
outputs: Arc<Mutex<VecDeque<MonitorHandle>>>,
|
||||
}
|
||||
|
||||
impl OutputManagerHandle {
|
||||
fn new() -> Self {
|
||||
let outputs = Arc::new(Mutex::new(VecDeque::new()));
|
||||
Self { outputs }
|
||||
}
|
||||
|
||||
/// Handle addition of the output.
|
||||
fn add_output(&self, output: WlOutput) {
|
||||
let mut outputs = self.outputs.lock().unwrap();
|
||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
||||
if position.is_none() {
|
||||
outputs.push_back(MonitorHandle::new(output));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle removal of the output.
|
||||
fn remove_output(&self, output: WlOutput) {
|
||||
let mut outputs = self.outputs.lock().unwrap();
|
||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
||||
if let Some(position) = position {
|
||||
outputs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all observed outputs.
|
||||
pub fn available_outputs(&self) -> VecDeque<MonitorHandle> {
|
||||
self.outputs.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,80 +101,6 @@ pub struct MonitorHandle {
|
||||
pub(crate) proxy: WlOutput,
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub(crate) fn new(proxy: WlOutput) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| info.name.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| info.id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
let dimensions = output_data.with_output_info(|info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find_map(|mode| mode.current.then_some(mode.dimensions))
|
||||
});
|
||||
|
||||
match dimensions {
|
||||
Some((width, height)) => (width as u32, height as u32),
|
||||
_ => (0, 0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| info.location).into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.with_output_info(|info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find_map(|mode| mode.current.then_some(mode.refresh_rate as u32))
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> i32 {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
output_data.scale_factor()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoMode> {
|
||||
let output_data = self.proxy.data::<OutputData>().unwrap();
|
||||
let modes = output_data.with_output_info(|info| info.modes.clone());
|
||||
|
||||
let monitor = self.clone();
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
PlatformVideoMode::Wayland(VideoMode {
|
||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
||||
refresh_rate_millihertz: mode.refresh_rate as u32,
|
||||
bit_depth: 32,
|
||||
monitor: monitor.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.native_identifier() == other.native_identifier()
|
||||
@@ -133,6 +127,78 @@ impl std::hash::Hash for MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub(crate) fn new(proxy: WlOutput) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| {
|
||||
format!("{} ({})", info.model, info.make)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
match sctk::output::with_output_info(&self.proxy, |info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find(|mode| mode.is_current)
|
||||
.map(|mode| mode.dimensions)
|
||||
}) {
|
||||
Some(Some((w, h))) => (w as u32, h as u32),
|
||||
_ => (0, 0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.location)
|
||||
.unwrap_or((0, 0))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32))
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> i32 {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoMode> {
|
||||
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let monitor = self.clone();
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
PlatformVideoMode::Wayland(VideoMode {
|
||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
||||
refresh_rate_millihertz: mode.refresh_rate as u32,
|
||||
bit_depth: 32,
|
||||
monitor: monitor.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
@@ -161,3 +227,21 @@ impl VideoMode {
|
||||
PlatformMonitorHandle::Wayland(self.monitor.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
self.output_manager.handle.available_outputs()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
13
src/platform_impl/linux/wayland/protocols.rs
Normal file
13
src/platform_impl/linux/wayland/protocols.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)]
|
||||
#![allow(non_upper_case_globals, non_snake_case, unused_imports)]
|
||||
#![allow(missing_docs, clippy::all)]
|
||||
|
||||
use wayland_client::protocol::wl_surface;
|
||||
use wayland_client::sys;
|
||||
use wayland_client::{AnonymousObject, Attached, Main, Proxy, ProxyMap};
|
||||
use wayland_commons::map::{Object, ObjectMetadata};
|
||||
use wayland_commons::smallvec;
|
||||
use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc};
|
||||
use wayland_commons::{Interface, MessageGroup};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/fractional_scale_v1.rs"));
|
||||
165
src/platform_impl/linux/wayland/seat/keyboard/handlers.rs
Normal file
165
src/platform_impl/linux/wayland/seat/keyboard/handlers.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
//! Handling of various keyboard events.
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_keyboard::KeyState;
|
||||
|
||||
use sctk::seat::keyboard::Event as KeyboardEvent;
|
||||
|
||||
use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent};
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::keymap;
|
||||
use super::KeyboardInner;
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_keyboard(
|
||||
event: KeyboardEvent<'_>,
|
||||
inner: &mut KeyboardInner,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
match event {
|
||||
KeyboardEvent::Enter { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
window_handle.has_focus.store(true, Ordering::Relaxed);
|
||||
|
||||
// Window gained focus.
|
||||
event_sink.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
|
||||
// Dispatch modifers changes that we've received before getting `Enter` event.
|
||||
if let Some(modifiers) = inner.pending_modifers_state.take() {
|
||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
||||
}
|
||||
|
||||
inner.target_window_id = Some(window_id);
|
||||
}
|
||||
KeyboardEvent::Leave { surface, .. } => {
|
||||
// Reset the id.
|
||||
inner.target_window_id = None;
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Notify that no modifiers are being pressed.
|
||||
if !inner.modifiers_state.borrow().is_empty() {
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
window_handle.has_focus.store(false, Ordering::Relaxed);
|
||||
|
||||
// Window lost focus.
|
||||
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
}
|
||||
KeyboardEvent::Key {
|
||||
rawkey,
|
||||
keysym,
|
||||
state,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let state = match state {
|
||||
KeyState::Pressed => ElementState::Pressed,
|
||||
KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
||||
|
||||
event_sink.push_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode,
|
||||
modifiers: *inner.modifiers_state.borrow(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
// Send ReceivedCharacter event only on ElementState::Pressed.
|
||||
if ElementState::Released == state {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(txt) = utf8 {
|
||||
for ch in txt.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Repeat {
|
||||
rawkey,
|
||||
keysym,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
||||
|
||||
event_sink.push_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
input: KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
scancode: rawkey,
|
||||
virtual_keycode,
|
||||
modifiers: *inner.modifiers_state.borrow(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
if let Some(txt) = utf8 {
|
||||
for ch in txt.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Modifiers { modifiers } => {
|
||||
let modifiers = ModifiersState::from(modifiers);
|
||||
if let Some(window_id) = inner.target_window_id {
|
||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
||||
|
||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
||||
} else {
|
||||
// Compositor must send modifiers after wl_keyboard::enter, however certain
|
||||
// compositors are still sending it before, so stash such events and send
|
||||
// them on wl_keyboard::enter.
|
||||
inner.pending_modifers_state = Some(modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
src/platform_impl/linux/wayland/seat/keyboard/keymap.rs
Normal file
192
src/platform_impl/linux/wayland/seat/keyboard/keymap.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
//! Convert Wayland keys to winit keys.
|
||||
|
||||
use crate::event::VirtualKeyCode;
|
||||
|
||||
pub fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
use sctk::seat::keyboard::keysyms;
|
||||
match keysym {
|
||||
// Numbers.
|
||||
keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1),
|
||||
keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2),
|
||||
keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3),
|
||||
keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4),
|
||||
keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5),
|
||||
keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6),
|
||||
keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7),
|
||||
keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8),
|
||||
keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9),
|
||||
keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0),
|
||||
// Letters.
|
||||
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
|
||||
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
|
||||
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
|
||||
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
|
||||
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
|
||||
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
|
||||
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
|
||||
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
|
||||
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
|
||||
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
|
||||
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
|
||||
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
|
||||
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
|
||||
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
|
||||
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
|
||||
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
|
||||
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
|
||||
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
|
||||
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
|
||||
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
|
||||
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
|
||||
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
|
||||
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
|
||||
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
|
||||
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
|
||||
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
|
||||
// Escape.
|
||||
keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape),
|
||||
// Function keys.
|
||||
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
|
||||
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
|
||||
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
|
||||
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
|
||||
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
|
||||
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
|
||||
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
|
||||
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
|
||||
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
|
||||
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
|
||||
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
|
||||
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
|
||||
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
|
||||
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
|
||||
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
|
||||
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
|
||||
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
|
||||
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
|
||||
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
|
||||
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
|
||||
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
|
||||
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
|
||||
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
|
||||
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
|
||||
// Flow control.
|
||||
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
|
||||
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
|
||||
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
|
||||
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
|
||||
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
|
||||
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
|
||||
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
|
||||
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
|
||||
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
|
||||
// Arrows.
|
||||
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
|
||||
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
|
||||
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
|
||||
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
|
||||
|
||||
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
|
||||
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
|
||||
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
|
||||
|
||||
keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose),
|
||||
keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret),
|
||||
|
||||
// Keypad.
|
||||
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
|
||||
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
|
||||
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
|
||||
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
|
||||
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
|
||||
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
|
||||
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
|
||||
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
|
||||
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
|
||||
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
|
||||
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
|
||||
// Misc.
|
||||
// => Some(VirtualKeyCode::AbntC1),
|
||||
// => Some(VirtualKeyCode::AbntC2),
|
||||
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus),
|
||||
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
|
||||
// => Some(VirtualKeyCode::Apps),
|
||||
keysyms::XKB_KEY_at => Some(VirtualKeyCode::At),
|
||||
// => Some(VirtualKeyCode::Ax),
|
||||
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
|
||||
keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator),
|
||||
// => Some(VirtualKeyCode::Capital),
|
||||
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
|
||||
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
|
||||
// => Some(VirtualKeyCode::Convert),
|
||||
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
|
||||
keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave),
|
||||
// => Some(VirtualKeyCode::Kana),
|
||||
keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji),
|
||||
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
|
||||
keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket),
|
||||
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
|
||||
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
|
||||
keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin),
|
||||
keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail),
|
||||
// => Some(VirtualKeyCode::MediaSelect),
|
||||
// => Some(VirtualKeyCode::MediaStop),
|
||||
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
|
||||
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk),
|
||||
keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute),
|
||||
// => Some(VirtualKeyCode::MyComputer),
|
||||
keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack),
|
||||
// => Some(VirtualKeyCode::NoConvert),
|
||||
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
|
||||
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
|
||||
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
|
||||
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd),
|
||||
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract),
|
||||
keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply),
|
||||
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide),
|
||||
keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal),
|
||||
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
|
||||
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
|
||||
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
|
||||
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
|
||||
keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left),
|
||||
keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up),
|
||||
keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right),
|
||||
keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down),
|
||||
// => Some(VirtualKeyCode::OEM102),
|
||||
keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period),
|
||||
// => Some(VirtualKeyCode::Playpause),
|
||||
keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power),
|
||||
keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack),
|
||||
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
|
||||
keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket),
|
||||
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
|
||||
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
|
||||
keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin),
|
||||
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
|
||||
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
|
||||
keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep),
|
||||
// => Some(VirtualKeyCode::Stop),
|
||||
// => Some(VirtualKeyCode::Sysrq),
|
||||
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
|
||||
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
|
||||
keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline),
|
||||
// => Some(VirtualKeyCode::Unlabeled),
|
||||
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
|
||||
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
|
||||
// => Some(VirtualKeyCode::Wake),
|
||||
// => Some(VirtualKeyCode::Webback),
|
||||
// => Some(VirtualKeyCode::WebFavorites),
|
||||
// => Some(VirtualKeyCode::WebForward),
|
||||
// => Some(VirtualKeyCode::WebHome),
|
||||
// => Some(VirtualKeyCode::WebRefresh),
|
||||
// => Some(VirtualKeyCode::WebSearch),
|
||||
// => Some(VirtualKeyCode::WebStop),
|
||||
keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen),
|
||||
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
|
||||
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
|
||||
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
|
||||
// Fallback.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1,373 +1,90 @@
|
||||
//! The keyboard input handling.
|
||||
//! Wayland keyboard handling.
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use calloop::timer::{TimeoutAction, Timer};
|
||||
use calloop::{LoopHandle, RegistrationToken};
|
||||
use log::warn;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
|
||||
use sctk::reexports::client::protocol::wl_keyboard::{
|
||||
Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat,
|
||||
};
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use crate::event::{ElementState, WindowEvent};
|
||||
use crate::keyboard::ModifiersState;
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
|
||||
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;
|
||||
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
|
||||
use sctk::seat::keyboard;
|
||||
|
||||
impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
wl_keyboard: &WlKeyboard,
|
||||
event: <WlKeyboard as Proxy>::Event,
|
||||
data: &KeyboardData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let seat_state = match state.seats.get_mut(&data.seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => return,
|
||||
};
|
||||
use crate::event::ModifiersState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
match event {
|
||||
WlKeyboardEvent::Keymap { format, fd, size } => match format {
|
||||
WEnum::Value(format) => match format {
|
||||
WlKeymapFormat::NoKeymap => {
|
||||
warn!("non-xkb compatible keymap")
|
||||
}
|
||||
WlKeymapFormat::XkbV1 => unsafe {
|
||||
seat_state
|
||||
.keyboard_state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.xkb_state
|
||||
.init_with_fd(fd, size as usize);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
WEnum::Unknown(value) => {
|
||||
warn!("unknown keymap format 0x{:x}", value)
|
||||
}
|
||||
},
|
||||
WlKeyboardEvent::Enter { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
mod handlers;
|
||||
mod keymap;
|
||||
|
||||
// Mark the window as focused.
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(true),
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Drop the repeat, if there were any.
|
||||
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
|
||||
|
||||
// The keyboard focus is considered as general focus.
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
|
||||
*data.window_id.lock().unwrap() = Some(window_id);
|
||||
|
||||
// HACK: this is just for GNOME not fixing their ordering issue of modifiers.
|
||||
if std::mem::take(&mut seat_state.modifiers_pending) {
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(seat_state.modifiers.into()),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
WlKeyboardEvent::Leave { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// NOTE: we should drop the repeat regardless whethere it was for the present
|
||||
// window of for the window which just went gone.
|
||||
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
|
||||
|
||||
// NOTE: The check whether the window exists is essential as we might get a
|
||||
// nil surface, regardless of what protocol says.
|
||||
match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().set_has_focus(false),
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Notify that no modifiers are being pressed.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// We don't need to update it above, because the next `Enter` will overwrite
|
||||
// anyway.
|
||||
*data.window_id.lock().unwrap() = None;
|
||||
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
}
|
||||
WlKeyboardEvent::Key {
|
||||
key,
|
||||
state: key_state,
|
||||
..
|
||||
} if key_state == WEnum::Value(WlKeyState::Pressed) => {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
seat_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
key,
|
||||
ElementState::Pressed,
|
||||
false,
|
||||
);
|
||||
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
let delay = match keyboard_state.repeat_info {
|
||||
RepeatInfo::Repeat { delay, .. } => delay,
|
||||
RepeatInfo::Disable => return,
|
||||
};
|
||||
|
||||
if !keyboard_state.xkb_state.key_repeats(key) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyboard_state.current_repeat = Some(key);
|
||||
|
||||
// NOTE terminate ongoing timer and start a new timer.
|
||||
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
let timer = Timer::from_duration(delay);
|
||||
let wl_keyboard = wl_keyboard.clone();
|
||||
keyboard_state.repeat_token = keyboard_state
|
||||
.loop_handle
|
||||
.insert_source(timer, move |_, _, state| {
|
||||
let data = wl_keyboard.data::<KeyboardData>().unwrap();
|
||||
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
|
||||
|
||||
// NOTE: The removed on event source is batched, but key change to
|
||||
// `None` is instant.
|
||||
let repeat_keycode =
|
||||
match seat_state.keyboard_state.as_ref().unwrap().current_repeat {
|
||||
Some(repeat_keycode) => repeat_keycode,
|
||||
None => return TimeoutAction::Drop,
|
||||
};
|
||||
|
||||
key_input(
|
||||
seat_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
repeat_keycode,
|
||||
ElementState::Pressed,
|
||||
true,
|
||||
);
|
||||
|
||||
// NOTE: the gap could change dynamically while repeat is going.
|
||||
match seat_state.keyboard_state.as_ref().unwrap().repeat_info {
|
||||
RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap),
|
||||
RepeatInfo::Disable => TimeoutAction::Drop,
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
WlKeyboardEvent::Key {
|
||||
key,
|
||||
state: key_state,
|
||||
..
|
||||
} if key_state == WEnum::Value(WlKeyState::Released) => {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
seat_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
key,
|
||||
ElementState::Released,
|
||||
false,
|
||||
);
|
||||
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
if keyboard_state.repeat_info != RepeatInfo::Disable
|
||||
&& keyboard_state.xkb_state.key_repeats(key)
|
||||
&& Some(key) == keyboard_state.current_repeat
|
||||
{
|
||||
keyboard_state.current_repeat = None;
|
||||
}
|
||||
}
|
||||
WlKeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
..
|
||||
} => {
|
||||
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.mods_state().into();
|
||||
|
||||
// HACK: part of the workaround from `WlKeyboardEvent::Enter`.
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
Some(window_id) => window_id,
|
||||
None => {
|
||||
seat_state.modifiers_pending = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(seat_state.modifiers.into()),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
WlKeyboardEvent::RepeatInfo { rate, delay } => {
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
keyboard_state.repeat_info = if rate == 0 {
|
||||
// Stop the repeat once we get a disable event.
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(repeat_token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(repeat_token);
|
||||
}
|
||||
RepeatInfo::Disable
|
||||
} else {
|
||||
let gap = Duration::from_micros(1_000_000 / rate as u64);
|
||||
let delay = Duration::from_millis(delay as u64);
|
||||
RepeatInfo::Repeat { gap, delay }
|
||||
};
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the keyboard on the current seat.
|
||||
#[derive(Debug)]
|
||||
pub struct KeyboardState {
|
||||
/// The underlying WlKeyboard.
|
||||
pub(crate) struct Keyboard {
|
||||
pub keyboard: WlKeyboard,
|
||||
|
||||
/// Loop handle to handle key repeat.
|
||||
pub loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// The state of the keyboard.
|
||||
pub xkb_state: KbdState,
|
||||
|
||||
/// The information about the repeat rate obtained from the compositor.
|
||||
pub repeat_info: RepeatInfo,
|
||||
|
||||
/// The token of the current handle inside the calloop's event loop.
|
||||
pub repeat_token: Option<RegistrationToken>,
|
||||
|
||||
/// The current repeat raw key.
|
||||
pub current_repeat: Option<u32>,
|
||||
}
|
||||
|
||||
impl KeyboardState {
|
||||
pub fn new(keyboard: WlKeyboard, loop_handle: LoopHandle<'static, WinitState>) -> Self {
|
||||
Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
xkb_state: KbdState::new().unwrap(),
|
||||
repeat_info: RepeatInfo::default(),
|
||||
repeat_token: None,
|
||||
current_repeat: None,
|
||||
}
|
||||
impl Keyboard {
|
||||
pub fn new(
|
||||
seat: &Attached<WlSeat>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Option<Self> {
|
||||
let mut inner = KeyboardInner::new(modifiers_state);
|
||||
let keyboard = keyboard::map_keyboard_repeat(
|
||||
loop_handle.clone(),
|
||||
seat,
|
||||
None,
|
||||
keyboard::RepeatKind::System,
|
||||
move |event, _, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_keyboard(event, &mut inner, winit_state);
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
Some(Self { keyboard })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KeyboardState {
|
||||
impl Drop for Keyboard {
|
||||
fn drop(&mut self) {
|
||||
if self.keyboard.version() >= 3 {
|
||||
if self.keyboard.as_ref().version() >= 3 {
|
||||
self.keyboard.release();
|
||||
}
|
||||
|
||||
if let Some(token) = self.repeat_token.take() {
|
||||
self.loop_handle.remove(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The rate at which a pressed key is repeated.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RepeatInfo {
|
||||
/// Keys will be repeated at the specified rate and delay.
|
||||
Repeat {
|
||||
/// The time between the key repeats.
|
||||
gap: Duration,
|
||||
struct KeyboardInner {
|
||||
/// Currently focused surface.
|
||||
target_window_id: Option<WindowId>,
|
||||
|
||||
/// Delay (in milliseconds) between a key press and the start of repetition.
|
||||
delay: Duration,
|
||||
},
|
||||
|
||||
/// Keys should not be repeated.
|
||||
Disable,
|
||||
}
|
||||
|
||||
impl Default for RepeatInfo {
|
||||
/// The default repeat rate is 25 keys per second with the delay of 200ms.
|
||||
/// A pending state of modifiers.
|
||||
///
|
||||
/// The values are picked based on the default in various compositors and Xorg.
|
||||
fn default() -> Self {
|
||||
Self::Repeat {
|
||||
gap: Duration::from_millis(40),
|
||||
delay: Duration::from_millis(200),
|
||||
}
|
||||
}
|
||||
/// This state is getting set if we've got a modifiers update
|
||||
/// before `Enter` event, which shouldn't happen in general, however
|
||||
/// some compositors are still doing so.
|
||||
pending_modifers_state: Option<ModifiersState>,
|
||||
|
||||
/// Current state of modifiers keys.
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
}
|
||||
|
||||
/// Keyboard user data.
|
||||
#[derive(Debug)]
|
||||
pub struct KeyboardData {
|
||||
/// The currently focused window surface. Could be `None` on bugged compositors, like mutter.
|
||||
window_id: Mutex<Option<WindowId>>,
|
||||
|
||||
/// The seat used to create this keyboard.
|
||||
seat: WlSeat,
|
||||
}
|
||||
|
||||
impl KeyboardData {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
impl KeyboardInner {
|
||||
fn new(modifiers_state: Rc<RefCell<ModifiersState>>) -> Self {
|
||||
Self {
|
||||
window_id: Default::default(),
|
||||
seat,
|
||||
target_window_id: None,
|
||||
pending_modifers_state: None,
|
||||
modifiers_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_input(
|
||||
seat_state: &mut WinitSeatState,
|
||||
event_sink: &mut EventSink,
|
||||
data: &KeyboardData,
|
||||
keycode: u32,
|
||||
state: ElementState,
|
||||
repeat: bool,
|
||||
) {
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
|
||||
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
let event = keyboard_state
|
||||
.xkb_state
|
||||
.process_key_event(keycode, state, repeat);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
impl From<keyboard::ModifiersState> for ModifiersState {
|
||||
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
|
||||
let mut wl_mods = ModifiersState::empty();
|
||||
wl_mods.set(ModifiersState::SHIFT, mods.shift);
|
||||
wl_mods.set(ModifiersState::CTRL, mods.ctrl);
|
||||
wl_mods.set(ModifiersState::ALT, mods.alt);
|
||||
wl_mods.set(ModifiersState::LOGO, mods.logo);
|
||||
wl_mods
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,212 +1,208 @@
|
||||
//! Seat handling.
|
||||
//! Seat handling and managing.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
||||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||
use sctk::environment::Environment;
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
use sctk::seat::pointer::ThemeManager;
|
||||
use sctk::seat::{SeatData, SeatListener};
|
||||
|
||||
use crate::keyboard::ModifiersState;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use super::env::WinitEnv;
|
||||
use super::event_loop::WinitState;
|
||||
use crate::event::ModifiersState;
|
||||
|
||||
mod keyboard;
|
||||
mod pointer;
|
||||
mod text_input;
|
||||
pub mod pointer;
|
||||
pub mod text_input;
|
||||
mod touch;
|
||||
|
||||
pub use pointer::relative_pointer::RelativePointerState;
|
||||
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
|
||||
pub use text_input::{TextInputState, ZwpTextInputV3Ext};
|
||||
use keyboard::Keyboard;
|
||||
use pointer::Pointers;
|
||||
use text_input::TextInput;
|
||||
use touch::Touch;
|
||||
|
||||
use keyboard::{KeyboardData, KeyboardState};
|
||||
use text_input::TextInputData;
|
||||
use touch::TouchPoint;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinitSeatState {
|
||||
/// The pointer bound on the seat.
|
||||
pointer: Option<Arc<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
/// The touch bound on the seat.
|
||||
touch: Option<WlTouch>,
|
||||
|
||||
/// The mapping from touched points to the surfaces they're present.
|
||||
touch_map: FnvHashMap<i32, TouchPoint>,
|
||||
|
||||
/// The text input bound on the seat.
|
||||
text_input: Option<Arc<ZwpTextInputV3>>,
|
||||
|
||||
/// The relative pointer bound on the seat.
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
|
||||
/// The keyboard bound on the seat.
|
||||
keyboard_state: Option<KeyboardState>,
|
||||
|
||||
/// The current modifiers state on the seat.
|
||||
modifiers: ModifiersState,
|
||||
|
||||
/// Wether we have pending modifiers.
|
||||
modifiers_pending: bool,
|
||||
pub struct SeatManager {
|
||||
/// Listener for seats.
|
||||
_seat_listener: SeatListener,
|
||||
}
|
||||
|
||||
impl WinitSeatState {
|
||||
pub fn new() -> Self {
|
||||
impl SeatManager {
|
||||
pub fn new(
|
||||
env: &Environment<WinitEnv>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
theme_manager: ThemeManager,
|
||||
) -> Self {
|
||||
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
|
||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>();
|
||||
let text_input_manager = env.get_global::<ZwpTextInputManagerV3>();
|
||||
|
||||
let mut inner = SeatManagerInner::new(
|
||||
theme_manager,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
loop_handle,
|
||||
);
|
||||
|
||||
// Handle existing seats.
|
||||
for seat in env.get_all_seats() {
|
||||
let seat_data = match sctk::seat::clone_seat_data(&seat) {
|
||||
Some(seat_data) => seat_data,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
inner.process_seat_update(&seat, &seat_data);
|
||||
}
|
||||
|
||||
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
|
||||
inner.process_seat_update(&seat, seat_data);
|
||||
});
|
||||
|
||||
Self {
|
||||
_seat_listener: seat_listener,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner state of the seat manager.
|
||||
struct SeatManagerInner {
|
||||
/// Currently observed seats.
|
||||
seats: Vec<SeatInfo>,
|
||||
|
||||
/// Loop handle.
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// Relative pointer manager.
|
||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
|
||||
/// Pointer constraints.
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
/// Text input manager.
|
||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
||||
|
||||
/// A theme manager.
|
||||
theme_manager: ThemeManager,
|
||||
}
|
||||
|
||||
impl SeatManagerInner {
|
||||
fn new(
|
||||
theme_manager: ThemeManager,
|
||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
) -> Self {
|
||||
Self {
|
||||
seats: Vec::new(),
|
||||
loop_handle,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
theme_manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle seats update from the `SeatListener`.
|
||||
pub fn process_seat_update(&mut self, seat: &Attached<WlSeat>, seat_data: &SeatData) {
|
||||
let detached_seat = seat.detach();
|
||||
|
||||
let position = self.seats.iter().position(|si| si.seat == detached_seat);
|
||||
let index = position.unwrap_or_else(|| {
|
||||
self.seats.push(SeatInfo::new(detached_seat));
|
||||
self.seats.len() - 1
|
||||
});
|
||||
|
||||
let seat_info = &mut self.seats[index];
|
||||
|
||||
// Pointer handling.
|
||||
if seat_data.has_pointer && !seat_data.defunct {
|
||||
if seat_info.pointer.is_none() {
|
||||
seat_info.pointer = Some(Pointers::new(
|
||||
seat,
|
||||
&self.theme_manager,
|
||||
&self.relative_pointer_manager,
|
||||
&self.pointer_constraints,
|
||||
seat_info.modifiers_state.clone(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
seat_info.pointer = None;
|
||||
}
|
||||
|
||||
// Handle keyboard.
|
||||
if seat_data.has_keyboard && !seat_data.defunct {
|
||||
if seat_info.keyboard.is_none() {
|
||||
seat_info.keyboard = Keyboard::new(
|
||||
seat,
|
||||
self.loop_handle.clone(),
|
||||
seat_info.modifiers_state.clone(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
seat_info.keyboard = None;
|
||||
}
|
||||
|
||||
// Handle touch.
|
||||
if seat_data.has_touch && !seat_data.defunct {
|
||||
if seat_info.touch.is_none() {
|
||||
seat_info.touch = Some(Touch::new(seat));
|
||||
}
|
||||
} else {
|
||||
seat_info.touch = None;
|
||||
}
|
||||
|
||||
// Handle text input.
|
||||
if let Some(text_input_manager) = self.text_input_manager.as_ref() {
|
||||
if seat_data.defunct {
|
||||
seat_info.text_input = None;
|
||||
} else if seat_info.text_input.is_none() {
|
||||
seat_info.text_input = Some(TextInput::new(seat, text_input_manager));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resources associtated with a given seat.
|
||||
struct SeatInfo {
|
||||
/// Seat to which this `SeatInfo` belongs.
|
||||
seat: WlSeat,
|
||||
|
||||
/// A keyboard handle with its repeat rate handling.
|
||||
keyboard: Option<Keyboard>,
|
||||
|
||||
/// All pointers we're using on a seat.
|
||||
pointer: Option<Pointers>,
|
||||
|
||||
/// Touch handling.
|
||||
touch: Option<Touch>,
|
||||
|
||||
/// Text input handling aka IME.
|
||||
text_input: Option<TextInput>,
|
||||
|
||||
/// The current state of modifiers observed in keyboard handler.
|
||||
///
|
||||
/// We keep modifiers state on a seat, since it's being used by pointer events as well.
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl SeatInfo {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
Self {
|
||||
seat,
|
||||
keyboard: None,
|
||||
pointer: None,
|
||||
touch: None,
|
||||
relative_pointer: None,
|
||||
text_input: None,
|
||||
touch_map: Default::default(),
|
||||
keyboard_state: None,
|
||||
modifiers: ModifiersState::empty(),
|
||||
modifiers_pending: false,
|
||||
modifiers_state: Rc::new(RefCell::new(ModifiersState::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandler for WinitState {
|
||||
fn seat_state(&mut self) -> &mut SeatState {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn new_capability(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
capability: SeatCapability,
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch if seat_state.touch.is_none() => {
|
||||
seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok();
|
||||
}
|
||||
SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => {
|
||||
let keyboard = seat.get_keyboard(queue_handle, KeyboardData::new(seat.clone()));
|
||||
seat_state.keyboard_state =
|
||||
Some(KeyboardState::new(keyboard, self.loop_handle.clone()));
|
||||
}
|
||||
SeatCapability::Pointer if seat_state.pointer.is_none() => {
|
||||
let surface = self.compositor_state.create_surface(queue_handle);
|
||||
let surface_id = surface.id();
|
||||
let pointer_data = WinitPointerData::new(seat.clone(), surface);
|
||||
let themed_pointer = self
|
||||
.seat_state
|
||||
.get_pointer_with_theme_and_data(
|
||||
queue_handle,
|
||||
&seat,
|
||||
ThemeSpec::System,
|
||||
pointer_data,
|
||||
)
|
||||
.expect("failed to create pointer with present capability.");
|
||||
|
||||
seat_state.relative_pointer = self.relative_pointer.as_ref().map(|manager| {
|
||||
manager.get_relative_pointer(
|
||||
themed_pointer.pointer(),
|
||||
queue_handle,
|
||||
sctk::globals::GlobalData,
|
||||
)
|
||||
});
|
||||
|
||||
let themed_pointer = Arc::new(themed_pointer);
|
||||
|
||||
// Register cursor surface.
|
||||
self.pointer_surfaces
|
||||
.insert(surface_id, themed_pointer.clone());
|
||||
|
||||
seat_state.pointer = Some(themed_pointer);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(text_input_state) = seat_state
|
||||
.text_input
|
||||
.is_none()
|
||||
.then_some(self.text_input_state.as_ref())
|
||||
.flatten()
|
||||
{
|
||||
seat_state.text_input = Some(Arc::new(text_input_state.get_text_input(
|
||||
&seat,
|
||||
queue_handle,
|
||||
TextInputData::default(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_capability(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
capability: SeatCapability,
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch => {
|
||||
if let Some(touch) = seat_state.touch.take() {
|
||||
if touch.version() >= 3 {
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
SeatCapability::Pointer => {
|
||||
if let Some(relative_pointer) = seat_state.relative_pointer.take() {
|
||||
relative_pointer.destroy();
|
||||
}
|
||||
|
||||
if let Some(pointer) = seat_state.pointer.take() {
|
||||
let pointer_data = pointer.pointer().winit_data();
|
||||
|
||||
// Remove the cursor from the mapping.
|
||||
let surface_id = pointer_data.cursor_surface().id();
|
||||
let _ = self.pointer_surfaces.remove(&surface_id);
|
||||
|
||||
// Remove the inner locks/confines before dropping the pointer.
|
||||
pointer_data.unlock_pointer();
|
||||
pointer_data.unconfine_pointer();
|
||||
|
||||
if pointer.pointer().version() >= 3 {
|
||||
pointer.pointer().release();
|
||||
}
|
||||
}
|
||||
}
|
||||
SeatCapability::Keyboard => {
|
||||
seat_state.keyboard_state = None;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(text_input) = seat_state.text_input.take() {
|
||||
text_input.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
fn new_seat(
|
||||
&mut self,
|
||||
_connection: &Connection,
|
||||
_queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
) {
|
||||
self.seats.insert(seat.id(), WinitSeatState::new());
|
||||
}
|
||||
|
||||
fn remove_seat(
|
||||
&mut self,
|
||||
_connection: &Connection,
|
||||
_queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
) {
|
||||
let _ = self.seats.remove(&seat.id());
|
||||
}
|
||||
}
|
||||
|
||||
sctk::delegate_seat!(WinitState);
|
||||
|
||||
82
src/platform_impl/linux/wayland/seat/pointer/data.rs
Normal file
82
src/platform_impl/linux/wayland/seat/pointer/data.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
//! Data which is used in pointer callbacks.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
|
||||
use crate::event::{ModifiersState, TouchPhase};
|
||||
|
||||
/// A data being used by pointer handlers.
|
||||
pub(super) struct PointerData {
|
||||
/// Winit's surface the pointer is currently over.
|
||||
pub surface: Option<WlSurface>,
|
||||
|
||||
/// Current modifiers state.
|
||||
///
|
||||
/// This refers a state of modifiers from `WlKeyboard` on
|
||||
/// the given seat.
|
||||
pub modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
|
||||
/// Pointer constraints.
|
||||
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
|
||||
/// Latest observed serial in pointer events.
|
||||
pub latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Latest observed serial in pointer enter events.
|
||||
pub latest_enter_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// The currently accumulated axis data on a pointer.
|
||||
pub axis_data: AxisData,
|
||||
}
|
||||
|
||||
impl PointerData {
|
||||
pub fn new(
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
surface: None,
|
||||
latest_serial: Rc::new(Cell::new(0)),
|
||||
latest_enter_serial: Rc::new(Cell::new(0)),
|
||||
confined_pointer,
|
||||
locked_pointer,
|
||||
modifiers_state,
|
||||
pointer_constraints,
|
||||
axis_data: AxisData::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Axis data.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct AxisData {
|
||||
/// Current state of the axis.
|
||||
pub axis_state: TouchPhase,
|
||||
|
||||
/// A buffer for `PixelDelta` event.
|
||||
pub axis_buffer: Option<(f32, f32)>,
|
||||
|
||||
/// A buffer for `LineDelta` event.
|
||||
pub axis_discrete_buffer: Option<(f32, f32)>,
|
||||
}
|
||||
|
||||
impl AxisData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
axis_state: TouchPhase::Ended,
|
||||
axis_buffer: None,
|
||||
axis_discrete_buffer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
327
src/platform_impl/linux/wayland/seat/pointer/handlers.rs
Normal file
327
src/platform_impl/linux/wayland/seat/pointer/handlers.rs
Normal file
@@ -0,0 +1,327 @@
|
||||
//! Handlers for the pointers we're using.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
|
||||
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
|
||||
};
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::{PointerData, WinitPointer};
|
||||
|
||||
// These values are comming from <linux/input-event-codes.h>.
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
const BTN_RIGHT: u32 = 0x111;
|
||||
const BTN_MIDDLE: u32 = 0x112;
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_pointer(
|
||||
pointer: ThemedPointer,
|
||||
event: PointerEvent,
|
||||
pointer_data: &Rc<RefCell<PointerData>>,
|
||||
winit_state: &mut WinitState,
|
||||
seat: WlSeat,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
let mut pointer_data = pointer_data.borrow_mut();
|
||||
match event {
|
||||
PointerEvent::Enter {
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
serial,
|
||||
..
|
||||
} => {
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
pointer_data.latest_enter_serial.replace(serial);
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
pointer_data.surface = Some(surface);
|
||||
|
||||
// Notify window that pointer entered the surface.
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_entered(winit_pointer);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
position,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Leave { surface, serial } => {
|
||||
pointer_data.surface = None;
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Notify a window that pointer is no longer observing it.
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_left(winit_pointer);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(surface);
|
||||
let window_handle = match winit_state.window_map.get(&window_id) {
|
||||
Some(w) => w,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
position,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Button {
|
||||
button,
|
||||
state,
|
||||
serial,
|
||||
..
|
||||
} => {
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let button = match button {
|
||||
BTN_LEFT => MouseButton::Left,
|
||||
BTN_RIGHT => MouseButton::Right,
|
||||
BTN_MIDDLE => MouseButton::Middle,
|
||||
button => MouseButton::Other(button as u16),
|
||||
};
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
state,
|
||||
button,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Axis { axis, value, .. } => {
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(surface);
|
||||
let window_handle = match winit_state.window_map.get(&window_id) {
|
||||
Some(w) => w,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if pointer.as_ref().version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
|
||||
// Old seat compatibility.
|
||||
match axis {
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
pointer_data.axis_data.axis_buffer = Some((x, y));
|
||||
|
||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisDiscrete { axis, discrete } => {
|
||||
let (mut x, mut y) = pointer_data
|
||||
.axis_data
|
||||
.axis_discrete_buffer
|
||||
.unwrap_or((0., 0.));
|
||||
|
||||
match axis {
|
||||
// Wayland sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x -= discrete as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
pointer_data.axis_data.axis_discrete_buffer = Some((x, y));
|
||||
|
||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisSource { .. } => (),
|
||||
PointerEvent::AxisStop { .. } => {
|
||||
pointer_data.axis_data.axis_state = TouchPhase::Ended;
|
||||
}
|
||||
PointerEvent::Frame => {
|
||||
let axis_buffer = pointer_data.axis_data.axis_buffer.take();
|
||||
let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take();
|
||||
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
let window_id = wayland::make_wid(surface);
|
||||
let window_handle = match winit_state.window_map.get(&window_id) {
|
||||
Some(w) => w,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let window_event = if let Some((x, y)) = axis_discrete_buffer {
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::LineDelta(x, y),
|
||||
phase: pointer_data.axis_data.axis_state,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
}
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
|
||||
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: pointer_data.axis_data.axis_state,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
event_sink.push_window_event(window_event, window_id);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
|
||||
if let RelativePointerEvent::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
winit_state.event_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
DeviceId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,492 +1,343 @@
|
||||
//! The pointer events.
|
||||
//! All pointer related handling.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use sctk::reexports::client::delegate_dispatch;
|
||||
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch};
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
|
||||
use sctk::compositor::SurfaceData;
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::seat::pointer::{PointerData, PointerDataExt};
|
||||
use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler};
|
||||
use sctk::seat::SeatState;
|
||||
use sctk::shell::xdg::frame::FrameClick;
|
||||
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
|
||||
use sctk::window::Window;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition};
|
||||
use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
|
||||
use crate::event::ModifiersState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::window::WinitFrame;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
|
||||
mod data;
|
||||
mod handlers;
|
||||
|
||||
pub mod relative_pointer;
|
||||
use data::PointerData;
|
||||
|
||||
impl PointerHandler for WinitState {
|
||||
fn pointer_frame(
|
||||
&mut self,
|
||||
connection: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
pointer: &WlPointer,
|
||||
events: &[PointerEvent],
|
||||
) {
|
||||
let seat = pointer.winit_data().seat();
|
||||
let seat_state = self.seats.get(&seat.id()).unwrap();
|
||||
/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`.
|
||||
pub struct WinitPointer {
|
||||
pointer: ThemedPointer,
|
||||
|
||||
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
/// Create confined pointers.
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
for event in events {
|
||||
let surface = &event.surface;
|
||||
/// Cursor to handle confine requests.
|
||||
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
// The parent surface.
|
||||
let parent_surface = match event.surface.data::<SurfaceData>() {
|
||||
Some(data) => data.parent_surface().unwrap_or(surface),
|
||||
None => continue,
|
||||
};
|
||||
/// Cursor to handle locked requests.
|
||||
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
|
||||
let window_id = wayland::make_wid(parent_surface);
|
||||
/// Latest observed serial in pointer events.
|
||||
/// used by Window::start_interactive_move()
|
||||
latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
// Ensure that window exists.
|
||||
let mut window = match self.windows.get_mut().get_mut(&window_id) {
|
||||
Some(window) => window.lock().unwrap(),
|
||||
None => continue,
|
||||
};
|
||||
/// Latest observed serial in pointer enter events.
|
||||
/// used by Window::set_cursor()
|
||||
latest_enter_serial: Rc<Cell<u32>>,
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let position: PhysicalPosition<f64> =
|
||||
LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor);
|
||||
/// Seat.
|
||||
seat: WlSeat,
|
||||
}
|
||||
|
||||
match event.kind {
|
||||
// Pointer movements on decorations.
|
||||
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. }
|
||||
if parent_surface != surface =>
|
||||
{
|
||||
if let Some(icon) =
|
||||
window.frame_point_moved(surface, event.position.0, event.position.1)
|
||||
{
|
||||
if let Some(pointer) = seat_state.pointer.as_ref() {
|
||||
let surface = pointer
|
||||
.pointer()
|
||||
.data::<WinitPointerData>()
|
||||
.unwrap()
|
||||
.cursor_surface();
|
||||
let scale_factor =
|
||||
surface.data::<SurfaceData>().unwrap().scale_factor();
|
||||
impl PartialEq for WinitPointer {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
*self.pointer == *other.pointer
|
||||
}
|
||||
}
|
||||
|
||||
let _ = pointer.set_cursor(
|
||||
connection,
|
||||
icon,
|
||||
self.shm.wl_shm(),
|
||||
surface,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEventKind::Leave { .. } if parent_surface != surface => {
|
||||
window.frame_point_left();
|
||||
}
|
||||
ref kind @ PointerEventKind::Press { button, serial, .. }
|
||||
| ref kind @ PointerEventKind::Release { button, serial, .. }
|
||||
if parent_surface != surface =>
|
||||
{
|
||||
let click = match wayland_button_to_winit(button) {
|
||||
MouseButton::Left => FrameClick::Normal,
|
||||
MouseButton::Right => FrameClick::Alternate,
|
||||
_ => continue,
|
||||
};
|
||||
let pressed = matches!(kind, PointerEventKind::Press { .. });
|
||||
impl Eq for WinitPointer {}
|
||||
|
||||
// Emulate click on the frame.
|
||||
window.frame_click(
|
||||
click,
|
||||
pressed,
|
||||
seat,
|
||||
serial,
|
||||
window_id,
|
||||
&mut self.window_compositor_updates,
|
||||
);
|
||||
}
|
||||
// Regular events on the main surface.
|
||||
PointerEventKind::Enter { .. } => {
|
||||
self.events_sink
|
||||
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
|
||||
impl WinitPointer {
|
||||
/// Set the cursor icon.
|
||||
///
|
||||
/// Providing `None` will hide the cursor.
|
||||
pub fn set_cursor(&self, cursor_icon: Option<CursorIcon>) {
|
||||
let cursor_icon = match cursor_icon {
|
||||
Some(cursor_icon) => cursor_icon,
|
||||
None => {
|
||||
// Hide the cursor.
|
||||
// WlPointer::set_cursor() expects the serial of the last *enter*
|
||||
// event (compare to to start_interactive_move()).
|
||||
(*self.pointer).set_cursor(self.latest_enter_serial.get(), None, 0, 0);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
|
||||
window.pointer_entered(pointer);
|
||||
}
|
||||
let cursors: &[&str] = match cursor_icon {
|
||||
CursorIcon::Alias => &["link"],
|
||||
CursorIcon::Arrow => &["arrow"],
|
||||
CursorIcon::Cell => &["plus"],
|
||||
CursorIcon::Copy => &["copy"],
|
||||
CursorIcon::Crosshair => &["crosshair"],
|
||||
CursorIcon::Default => &["left_ptr"],
|
||||
CursorIcon::Hand => &["hand2", "hand1"],
|
||||
CursorIcon::Help => &["question_arrow"],
|
||||
CursorIcon::Move => &["move"],
|
||||
CursorIcon::Grab => &["openhand", "grab"],
|
||||
CursorIcon::Grabbing => &["closedhand", "grabbing"],
|
||||
CursorIcon::Progress => &["progress"],
|
||||
CursorIcon::AllScroll => &["all-scroll"],
|
||||
CursorIcon::ContextMenu => &["context-menu"],
|
||||
|
||||
// Set the currently focused surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
|
||||
CursorIcon::NoDrop => &["no-drop", "circle"],
|
||||
CursorIcon::NotAllowed => &["crossed_circle"],
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEventKind::Leave { .. } => {
|
||||
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
|
||||
window.pointer_left(pointer);
|
||||
}
|
||||
// Resize cursors
|
||||
CursorIcon::EResize => &["right_side"],
|
||||
CursorIcon::NResize => &["top_side"],
|
||||
CursorIcon::NeResize => &["top_right_corner"],
|
||||
CursorIcon::NwResize => &["top_left_corner"],
|
||||
CursorIcon::SResize => &["bottom_side"],
|
||||
CursorIcon::SeResize => &["bottom_right_corner"],
|
||||
CursorIcon::SwResize => &["bottom_left_corner"],
|
||||
CursorIcon::WResize => &["left_side"],
|
||||
CursorIcon::EwResize => &["h_double_arrow"],
|
||||
CursorIcon::NsResize => &["v_double_arrow"],
|
||||
CursorIcon::NwseResize => &["bd_double_arrow", "size_fdiag"],
|
||||
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
|
||||
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
|
||||
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
|
||||
CursorIcon::Text => &["text", "xterm"],
|
||||
CursorIcon::VerticalText => &["vertical-text"],
|
||||
|
||||
// Remove the active surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = None;
|
||||
CursorIcon::Wait => &["watch"],
|
||||
|
||||
self.events_sink
|
||||
.push_window_event(WindowEvent::CursorLeft { device_id }, window_id);
|
||||
}
|
||||
PointerEventKind::Motion { .. } => {
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
ref kind @ PointerEventKind::Press { button, serial, .. }
|
||||
| ref kind @ PointerEventKind::Release { button, serial, .. } => {
|
||||
// Update the last button serial.
|
||||
pointer
|
||||
.winit_data()
|
||||
.inner
|
||||
.lock()
|
||||
.unwrap()
|
||||
.latest_button_serial = serial;
|
||||
CursorIcon::ZoomIn => &["zoom-in"],
|
||||
CursorIcon::ZoomOut => &["zoom-out"],
|
||||
};
|
||||
|
||||
let button = wayland_button_to_winit(button);
|
||||
let state = if matches!(kind, PointerEventKind::Press { .. }) {
|
||||
ElementState::Pressed
|
||||
} else {
|
||||
ElementState::Released
|
||||
};
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEventKind::Axis {
|
||||
horizontal,
|
||||
vertical,
|
||||
..
|
||||
} => {
|
||||
// Get the current phase.
|
||||
let mut pointer_data = pointer.winit_data().inner.lock().unwrap();
|
||||
|
||||
let has_discrete_scroll = horizontal.discrete != 0 || vertical.discrete != 0;
|
||||
|
||||
// Figure out what to do about start/ended phases here.
|
||||
//
|
||||
// Figure out how to deal with `Started`. Also the `Ended` is not guaranteed
|
||||
// to be sent for mouse wheels.
|
||||
let phase = if horizontal.stop || vertical.stop {
|
||||
TouchPhase::Ended
|
||||
} else {
|
||||
match pointer_data.phase {
|
||||
// Descrete scroll only results in moved events.
|
||||
_ if has_discrete_scroll => TouchPhase::Moved,
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
};
|
||||
|
||||
// Update the phase.
|
||||
pointer_data.phase = phase;
|
||||
|
||||
// Mice events have both pixel and discrete delta's at the same time. So prefer
|
||||
// the descrite values if they are present.
|
||||
let delta = if has_discrete_scroll {
|
||||
// XXX Wayland sign convention is the inverse of winit.
|
||||
MouseScrollDelta::LineDelta(
|
||||
(-horizontal.discrete) as f32,
|
||||
(-vertical.discrete) as f32,
|
||||
)
|
||||
} else {
|
||||
// XXX Wayland sign convention is the inverse of winit.
|
||||
MouseScrollDelta::PixelDelta(
|
||||
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
|
||||
.to_physical(scale_factor),
|
||||
)
|
||||
};
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id,
|
||||
delta,
|
||||
phase,
|
||||
},
|
||||
window_id,
|
||||
)
|
||||
}
|
||||
let serial = Some(self.latest_enter_serial.get());
|
||||
for cursor in cursors {
|
||||
if self.pointer.set_cursor(cursor, serial).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPointerData {
|
||||
/// The surface associated with this pointer, which is used for icons.
|
||||
cursor_surface: WlSurface,
|
||||
|
||||
/// The inner winit data associated with the pointer.
|
||||
inner: Mutex<WinitPointerDataInner>,
|
||||
|
||||
/// The data required by the sctk.
|
||||
sctk_data: PointerData,
|
||||
}
|
||||
|
||||
impl WinitPointerData {
|
||||
pub fn new(seat: WlSeat, surface: WlSurface) -> Self {
|
||||
Self {
|
||||
cursor_surface: surface,
|
||||
inner: Mutex::new(WinitPointerDataInner::default()),
|
||||
sctk_data: PointerData::new(seat),
|
||||
}
|
||||
warn!("Failed to set cursor to {:?}", cursor_icon);
|
||||
}
|
||||
|
||||
pub fn lock_pointer(
|
||||
&self,
|
||||
pointer_constraints: &PointerConstraintsState,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if inner.locked_pointer.is_none() {
|
||||
inner.locked_pointer = Some(pointer_constraints.lock_pointer(
|
||||
surface,
|
||||
pointer,
|
||||
None,
|
||||
Lifetime::Persistent,
|
||||
queue_handle,
|
||||
GlobalData,
|
||||
));
|
||||
}
|
||||
}
|
||||
/// Confine the pointer to a surface.
|
||||
pub fn confine(&self, surface: &WlSurface) {
|
||||
let pointer_constraints = match &self.pointer_constraints {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None => return,
|
||||
};
|
||||
|
||||
pub fn unlock_pointer(&self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(locked_pointer) = inner.locked_pointer.take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
}
|
||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
||||
Some(confined_pointer) => confined_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
pub fn confine_pointer(
|
||||
&self,
|
||||
pointer_constraints: &PointerConstraintsState,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
self.inner.lock().unwrap().confined_pointer = Some(pointer_constraints.confine_pointer(
|
||||
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
|
||||
pointer_constraints,
|
||||
surface,
|
||||
pointer,
|
||||
None,
|
||||
Lifetime::Persistent,
|
||||
queue_handle,
|
||||
GlobalData,
|
||||
&self.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn unconfine_pointer(&self) {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
if let Some(confined_pointer) = inner.confined_pointer.as_ref() {
|
||||
/// Tries to unconfine the pointer if the current pointer is confined.
|
||||
pub fn unconfine(&self) {
|
||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
||||
Some(confined_pointer) => confined_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut confined_pointer = confined_pointer.borrow_mut();
|
||||
|
||||
if let Some(confined_pointer) = confined_pointer.take() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// Seat associated with this pointer.
|
||||
pub fn seat(&self) -> &WlSeat {
|
||||
self.sctk_data.seat()
|
||||
pub fn lock(&self, surface: &WlSurface) {
|
||||
let pointer_constraints = match &self.pointer_constraints {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
|
||||
pointer_constraints,
|
||||
surface,
|
||||
&self.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
/// The WlSurface used to set cursor theme.
|
||||
pub fn cursor_surface(&self) -> &WlSurface {
|
||||
&self.cursor_surface
|
||||
pub fn unlock(&self) {
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut locked_pointer = locked_pointer.borrow_mut();
|
||||
|
||||
if let Some(locked_pointer) = locked_pointer.take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// Active window.
|
||||
pub fn focused_window(&self) -> Option<WindowId> {
|
||||
self.inner.lock().unwrap().surface
|
||||
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
|
||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
||||
Some(locked_pointer) => locked_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let locked_pointer = locked_pointer.borrow_mut();
|
||||
if let Some(locked_pointer) = locked_pointer.as_ref() {
|
||||
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Last button serial.
|
||||
pub fn latest_button_serial(&self) -> u32 {
|
||||
self.inner.lock().unwrap().latest_button_serial
|
||||
pub fn drag_window(&self, window: &Window<WinitFrame>) {
|
||||
// WlPointer::setart_interactive_move() expects the last serial of *any*
|
||||
// pointer event (compare to set_cursor()).
|
||||
window.start_interactive_move(&self.seat, self.latest_serial.get());
|
||||
}
|
||||
}
|
||||
|
||||
/// Last enter serial.
|
||||
pub fn latest_enter_serial(&self) -> u32 {
|
||||
self.sctk_data.latest_enter_serial().unwrap_or_default()
|
||||
}
|
||||
/// A pointer wrapper for easy releasing and managing pointers.
|
||||
pub(super) struct Pointers {
|
||||
/// A pointer itself.
|
||||
pointer: ThemedPointer,
|
||||
|
||||
pub fn set_locked_cursor_position(&self, surface_x: f64, surface_y: f64) {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
if let Some(locked_pointer) = inner.locked_pointer.as_ref() {
|
||||
locked_pointer.set_cursor_position_hint(surface_x, surface_y);
|
||||
/// A relative pointer handler.
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
|
||||
/// Confined pointer.
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
/// Locked pointer.
|
||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
||||
}
|
||||
|
||||
impl Pointers {
|
||||
pub(super) fn new(
|
||||
seat: &Attached<WlSeat>,
|
||||
theme_manager: &ThemeManager,
|
||||
relative_pointer_manager: &Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
pointer_constraints: &Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
let confined_pointer = Rc::new(RefCell::new(None));
|
||||
let locked_pointer = Rc::new(RefCell::new(None));
|
||||
|
||||
let pointer_data = Rc::new(RefCell::new(PointerData::new(
|
||||
confined_pointer.clone(),
|
||||
locked_pointer.clone(),
|
||||
pointer_constraints.clone(),
|
||||
modifiers_state,
|
||||
)));
|
||||
|
||||
let pointer_seat = seat.detach();
|
||||
let pointer = theme_manager.theme_pointer_with_impl(
|
||||
seat,
|
||||
move |event, pointer, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_pointer(
|
||||
pointer,
|
||||
event,
|
||||
&pointer_data,
|
||||
winit_state,
|
||||
pointer_seat.clone(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Setup relative_pointer if it's available.
|
||||
let relative_pointer = relative_pointer_manager
|
||||
.as_ref()
|
||||
.map(|relative_pointer_manager| {
|
||||
init_relative_pointer(relative_pointer_manager, &pointer)
|
||||
});
|
||||
|
||||
Self {
|
||||
pointer,
|
||||
relative_pointer,
|
||||
confined_pointer,
|
||||
locked_pointer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinitPointerData {
|
||||
impl Drop for Pointers {
|
||||
fn drop(&mut self) {
|
||||
self.cursor_surface.destroy();
|
||||
}
|
||||
}
|
||||
// Drop relative pointer.
|
||||
if let Some(relative_pointer) = self.relative_pointer.take() {
|
||||
relative_pointer.destroy();
|
||||
}
|
||||
|
||||
impl PointerDataExt for WinitPointerData {
|
||||
fn pointer_data(&self) -> &PointerData {
|
||||
&self.sctk_data
|
||||
}
|
||||
}
|
||||
// Drop confined pointer.
|
||||
if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPointerDataInner {
|
||||
/// The associated locked pointer.
|
||||
locked_pointer: Option<ZwpLockedPointerV1>,
|
||||
|
||||
/// The associated confined pointer.
|
||||
confined_pointer: Option<ZwpConfinedPointerV1>,
|
||||
|
||||
/// Serial of the last button event.
|
||||
latest_button_serial: u32,
|
||||
|
||||
/// Currently focused window.
|
||||
surface: Option<WindowId>,
|
||||
|
||||
/// Current axis phase.
|
||||
phase: TouchPhase,
|
||||
}
|
||||
|
||||
impl Drop for WinitPointerDataInner {
|
||||
fn drop(&mut self) {
|
||||
if let Some(locked_pointer) = self.locked_pointer.take() {
|
||||
// Drop lock ponter.
|
||||
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
|
||||
if let Some(confined_pointer) = self.confined_pointer.take() {
|
||||
confined_pointer.destroy();
|
||||
// Drop the pointer itself in case it's possible.
|
||||
if self.pointer.as_ref().version() >= 3 {
|
||||
self.pointer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WinitPointerDataInner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
surface: None,
|
||||
locked_pointer: None,
|
||||
confined_pointer: None,
|
||||
latest_button_serial: 0,
|
||||
phase: TouchPhase::Ended,
|
||||
}
|
||||
}
|
||||
pub(super) fn init_relative_pointer(
|
||||
relative_pointer_manager: &ZwpRelativePointerManagerV1,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpRelativePointerV1 {
|
||||
let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer);
|
||||
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_relative_pointer(event, winit_state);
|
||||
});
|
||||
|
||||
relative_pointer.detach()
|
||||
}
|
||||
|
||||
/// Convert the Wayland button into winit.
|
||||
fn wayland_button_to_winit(button: u32) -> MouseButton {
|
||||
// These values are coming from <linux/input-event-codes.h>.
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
const BTN_RIGHT: u32 = 0x111;
|
||||
const BTN_MIDDLE: u32 = 0x112;
|
||||
const BTN_SIDE: u32 = 0x113;
|
||||
const BTN_EXTRA: u32 = 0x114;
|
||||
const BTN_FORWARD: u32 = 0x115;
|
||||
const BTN_BACK: u32 = 0x116;
|
||||
pub(super) fn init_confined_pointer(
|
||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpConfinedPointerV1 {
|
||||
let confined_pointer =
|
||||
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent);
|
||||
|
||||
match button {
|
||||
BTN_LEFT => MouseButton::Left,
|
||||
BTN_RIGHT => MouseButton::Right,
|
||||
BTN_MIDDLE => MouseButton::Middle,
|
||||
BTN_BACK | BTN_SIDE => MouseButton::Back,
|
||||
BTN_FORWARD | BTN_EXTRA => MouseButton::Forward,
|
||||
button => MouseButton::Other(button as u16),
|
||||
}
|
||||
confined_pointer.quick_assign(move |_, _, _| {});
|
||||
|
||||
confined_pointer.detach()
|
||||
}
|
||||
|
||||
pub trait WinitPointerDataExt {
|
||||
fn winit_data(&self) -> &WinitPointerData;
|
||||
}
|
||||
pub(super) fn init_locked_pointer(
|
||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpLockedPointerV1 {
|
||||
let locked_pointer =
|
||||
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
|
||||
|
||||
impl WinitPointerDataExt for WlPointer {
|
||||
fn winit_data(&self) -> &WinitPointerData {
|
||||
self.data::<WinitPointerData>()
|
||||
.expect("failed to get pointer data.")
|
||||
}
|
||||
}
|
||||
locked_pointer.quick_assign(move |_, _, _| {});
|
||||
|
||||
pub struct PointerConstraintsState {
|
||||
pointer_constraints: ZwpPointerConstraintsV1,
|
||||
locked_pointer.detach()
|
||||
}
|
||||
|
||||
impl PointerConstraintsState {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let pointer_constraints = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self {
|
||||
pointer_constraints,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PointerConstraintsState {
|
||||
type Target = ZwpPointerConstraintsV1;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.pointer_constraints
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPointerConstraintsV1, GlobalData, WinitState> for PointerConstraintsState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpPointerConstraintsV1,
|
||||
_event: <ZwpPointerConstraintsV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpLockedPointerV1, GlobalData, WinitState> for PointerConstraintsState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpLockedPointerV1,
|
||||
_event: <ZwpLockedPointerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpConfinedPointerV1, GlobalData, WinitState> for PointerConstraintsState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpConfinedPointerV1,
|
||||
_event: <ZwpConfinedPointerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState);
|
||||
delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState);
|
||||
delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState);
|
||||
delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState);
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
//! Relative pointer.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::{delegate_dispatch, Dispatch};
|
||||
use sctk::reexports::client::{Connection, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1},
|
||||
};
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
|
||||
use crate::event::DeviceEvent;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
/// Wrapper around the relative pointer.
|
||||
pub struct RelativePointerState {
|
||||
manager: ZwpRelativePointerManagerV1,
|
||||
}
|
||||
|
||||
impl RelativePointerState {
|
||||
/// Create new relative pointer manager.
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { manager })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for RelativePointerState {
|
||||
type Target = ZwpRelativePointerManagerV1;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.manager
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpRelativePointerManagerV1, GlobalData, WinitState> for RelativePointerState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpRelativePointerManagerV1,
|
||||
_event: <ZwpRelativePointerManagerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
_proxy: &ZwpRelativePointerV1,
|
||||
event: <ZwpRelativePointerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
if let zwp_relative_pointer_v1::Event::RelativeMotion {
|
||||
dx_unaccel,
|
||||
dy_unaccel,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
state.events_sink.push_device_event(
|
||||
DeviceEvent::MouseMotion {
|
||||
delta: (dx_unaccel, dy_unaccel),
|
||||
},
|
||||
super::DeviceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpRelativePointerV1: GlobalData] => RelativePointerState);
|
||||
delegate_dispatch!(WinitState: [ZwpRelativePointerManagerV1: GlobalData] => RelativePointerState);
|
||||
122
src/platform_impl/linux/wayland/seat/text_input/handlers.rs
Normal file
122
src/platform_impl/linux/wayland/seat/text_input/handlers.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
//! Handling of IME events.
|
||||
|
||||
use sctk::reexports::client::Main;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
|
||||
Event as TextInputEvent, ZwpTextInputV3,
|
||||
};
|
||||
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
|
||||
use super::{Preedit, TextInputHandler, TextInputInner, ZwpTextInputV3Ext};
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_text_input(
|
||||
text_input: Main<ZwpTextInputV3>,
|
||||
inner: &mut TextInputInner,
|
||||
event: TextInputEvent,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
match event {
|
||||
TextInputEvent::Enter { surface } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
inner.target_window_id = Some(window_id);
|
||||
|
||||
// Enable text input on that surface.
|
||||
if window_handle.ime_allowed.get() {
|
||||
text_input.enable();
|
||||
text_input.set_content_type_by_purpose(window_handle.ime_purpose.get());
|
||||
text_input.commit();
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
|
||||
}
|
||||
|
||||
// Notify a window we're currently over about text input handler.
|
||||
let text_input_handler = TextInputHandler {
|
||||
text_input: text_input.detach(),
|
||||
};
|
||||
window_handle.text_input_entered(text_input_handler);
|
||||
}
|
||||
TextInputEvent::Leave { surface } => {
|
||||
// Always issue a disable.
|
||||
text_input.disable();
|
||||
text_input.commit();
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
inner.target_window_id = None;
|
||||
|
||||
// Remove text input handler from the window we're leaving.
|
||||
let text_input_handler = TextInputHandler {
|
||||
text_input: text_input.detach(),
|
||||
};
|
||||
window_handle.text_input_left(text_input_handler);
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
|
||||
}
|
||||
TextInputEvent::PreeditString {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
} => {
|
||||
let text = text.unwrap_or_default();
|
||||
let cursor_begin = usize::try_from(cursor_begin)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then(|| idx));
|
||||
let cursor_end = usize::try_from(cursor_end)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then(|| idx));
|
||||
|
||||
inner.pending_preedit = Some(Preedit {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
});
|
||||
}
|
||||
TextInputEvent::CommitString { text } => {
|
||||
// Update currenly commited string and reset previous preedit.
|
||||
inner.pending_preedit = None;
|
||||
inner.pending_commit = Some(text.unwrap_or_default());
|
||||
}
|
||||
TextInputEvent::Done { .. } => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Clear preedit at the start of `Done`.
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// Send `Commit`.
|
||||
if let Some(text) = inner.pending_commit.take() {
|
||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
|
||||
}
|
||||
|
||||
// Send preedit.
|
||||
if let Some(preedit) = inner.pending_preedit.take() {
|
||||
let cursor_range = preedit
|
||||
.cursor_begin
|
||||
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -1,174 +1,23 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
|
||||
use sctk::reexports::client::delegate_dispatch;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Dispatch;
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event as TextInputEvent;
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
|
||||
ContentHint, ContentPurpose, ZwpTextInputV3,
|
||||
};
|
||||
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
use crate::window::ImePurpose;
|
||||
|
||||
pub struct TextInputState {
|
||||
text_input_manager: ZwpTextInputManagerV3,
|
||||
mod handlers;
|
||||
|
||||
/// A handler for text input that we're advertising for `WindowHandle`.
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct TextInputHandler {
|
||||
text_input: ZwpTextInputV3,
|
||||
}
|
||||
|
||||
impl TextInputState {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { text_input_manager })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TextInputState {
|
||||
type Target = ZwpTextInputManagerV3;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.text_input_manager
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTextInputManagerV3, GlobalData, WinitState> for TextInputState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpTextInputManagerV3,
|
||||
_event: <ZwpTextInputManagerV3 as Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
text_input: &ZwpTextInputV3,
|
||||
event: <ZwpTextInputV3 as Proxy>::Event,
|
||||
data: &TextInputData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut text_input_data = data.inner.lock().unwrap();
|
||||
match event {
|
||||
TextInputEvent::Enter { surface } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
text_input_data.surface = Some(surface);
|
||||
|
||||
let mut window = match windows.get(&window_id) {
|
||||
Some(window) => window.lock().unwrap(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if window.ime_allowed() {
|
||||
text_input.enable();
|
||||
text_input.set_content_type_by_purpose(window.ime_purpose());
|
||||
text_input.commit();
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
|
||||
}
|
||||
|
||||
window.text_input_entered(text_input);
|
||||
}
|
||||
TextInputEvent::Leave { surface } => {
|
||||
text_input_data.surface = None;
|
||||
|
||||
// Always issue a disable.
|
||||
text_input.disable();
|
||||
text_input.commit();
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// XXX this check is essential, because `leave` could have a
|
||||
// refence to nil surface...
|
||||
let mut window = match windows.get(&window_id) {
|
||||
Some(window) => window.lock().unwrap(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
window.text_input_left(text_input);
|
||||
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
|
||||
}
|
||||
TextInputEvent::PreeditString {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
} => {
|
||||
let text = text.unwrap_or_default();
|
||||
let cursor_begin = usize::try_from(cursor_begin)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
|
||||
let cursor_end = usize::try_from(cursor_end)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
|
||||
|
||||
text_input_data.pending_preedit = Some(Preedit {
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
})
|
||||
}
|
||||
TextInputEvent::CommitString { text } => {
|
||||
text_input_data.pending_preedit = None;
|
||||
text_input_data.pending_commit = text;
|
||||
}
|
||||
TextInputEvent::Done { .. } => {
|
||||
let window_id = match text_input_data.surface.as_ref() {
|
||||
Some(surface) => wayland::make_wid(surface),
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Clear preedit at the start of `Done`.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// Send `Commit`.
|
||||
if let Some(text) = text_input_data.pending_commit.take() {
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
|
||||
}
|
||||
|
||||
// Send preedit.
|
||||
if let Some(preedit) = text_input_data.pending_preedit.take() {
|
||||
let cursor_range = preedit
|
||||
.cursor_begin
|
||||
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
|
||||
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
TextInputEvent::DeleteSurroundingText { .. } => {
|
||||
// Not handled.
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ZwpTextInputV3Ext {
|
||||
trait ZwpTextInputV3Ext {
|
||||
fn set_content_type_by_purpose(&self, purpose: ImePurpose);
|
||||
}
|
||||
|
||||
@@ -183,30 +32,81 @@ impl ZwpTextInputV3Ext for ZwpTextInputV3 {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Data associated with the text input.
|
||||
#[derive(Default)]
|
||||
pub struct TextInputData {
|
||||
inner: std::sync::Mutex<TextInputDataInner>,
|
||||
impl TextInputHandler {
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, x: i32, y: i32) {
|
||||
self.text_input.set_cursor_rectangle(x, y, 0, 0);
|
||||
self.text_input.commit();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_content_type_by_purpose(&self, purpose: ImePurpose) {
|
||||
self.text_input.set_content_type_by_purpose(purpose);
|
||||
self.text_input.commit();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_input_allowed(&self, allowed: Option<ImePurpose>) {
|
||||
if let Some(purpose) = allowed {
|
||||
self.text_input.set_content_type_by_purpose(purpose);
|
||||
self.text_input.enable();
|
||||
} else {
|
||||
self.text_input.disable();
|
||||
}
|
||||
|
||||
self.text_input.commit();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextInputDataInner {
|
||||
/// The `WlSurface` we're performing input to.
|
||||
surface: Option<WlSurface>,
|
||||
/// A wrapper around text input to automatically destroy the object on `Drop`.
|
||||
pub struct TextInput {
|
||||
text_input: Attached<ZwpTextInputV3>,
|
||||
}
|
||||
|
||||
/// The commit to submit on `done`.
|
||||
impl TextInput {
|
||||
pub fn new(seat: &Attached<WlSeat>, text_input_manager: &ZwpTextInputManagerV3) -> Self {
|
||||
let text_input = text_input_manager.get_text_input(seat);
|
||||
let mut text_input_inner = TextInputInner::new();
|
||||
text_input.quick_assign(move |text_input, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state);
|
||||
});
|
||||
|
||||
let text_input: Attached<ZwpTextInputV3> = text_input.into();
|
||||
|
||||
Self { text_input }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TextInput {
|
||||
fn drop(&mut self) {
|
||||
self.text_input.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
struct TextInputInner {
|
||||
/// Currently focused surface.
|
||||
target_window_id: Option<WindowId>,
|
||||
|
||||
/// Pending commit event which will be dispatched on `text_input_v3::Done`.
|
||||
pending_commit: Option<String>,
|
||||
|
||||
/// The preedit to submit on `done`.
|
||||
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
|
||||
pending_preedit: Option<Preedit>,
|
||||
}
|
||||
|
||||
/// The state of the preedit.
|
||||
struct Preedit {
|
||||
text: String,
|
||||
cursor_begin: Option<usize>,
|
||||
cursor_end: Option<usize>,
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState);
|
||||
delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState);
|
||||
impl TextInputInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
target_window_id: None,
|
||||
pending_commit: None,
|
||||
pending_preedit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
144
src/platform_impl/linux/wayland/seat/touch/handlers.rs
Normal file
144
src/platform_impl/linux/wayland/seat/touch/handlers.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
//! Various handlers for touch events.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{TouchPhase, WindowEvent};
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::{TouchInner, TouchPoint};
|
||||
|
||||
/// Handle WlTouch events.
|
||||
#[inline]
|
||||
pub(super) fn handle_touch(
|
||||
event: TouchEvent,
|
||||
inner: &mut TouchInner,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
|
||||
match event {
|
||||
TouchEvent::Down {
|
||||
surface, id, x, y, ..
|
||||
} => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
let window_handle = match winit_state.window_map.get(&window_id) {
|
||||
Some(w) => w,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let position = LogicalPosition::new(x, y);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Started,
|
||||
location: position.to_physical(scale_factor),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
|
||||
// For `TouchEvent::Up` we don't receive a position, so we're tracking active
|
||||
// touch points. Update either a known touch id or register a new one.
|
||||
if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) {
|
||||
inner.touch_points[i].position = position;
|
||||
} else {
|
||||
inner
|
||||
.touch_points
|
||||
.push(TouchPoint::new(surface, position, id));
|
||||
}
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let window_handle = match winit_state.window_map.get(&window_id) {
|
||||
Some(w) => w,
|
||||
_ => return,
|
||||
};
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Ended,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let window_handle = match winit_state.window_map.get(&window_id) {
|
||||
Some(w) => w,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
touch_point.position = LogicalPosition::new(x, y);
|
||||
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Moved,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => {
|
||||
for touch_point in inner.touch_points.drain(..) {
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let window_handle = match winit_state.window_map.get(&window_id) {
|
||||
Some(w) => w,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let scale_factor = window_handle.scale_factor();
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: touch_point.id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -3,197 +3,76 @@
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
|
||||
use sctk::seat::touch::{TouchData, TouchHandler};
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{Touch, TouchPhase, WindowEvent};
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
|
||||
impl TouchHandler for WinitState {
|
||||
fn down(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
touch: &WlTouch,
|
||||
_: u32,
|
||||
_: u32,
|
||||
surface: WlSurface,
|
||||
id: i32,
|
||||
position: (f64, f64),
|
||||
) {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
mod handlers;
|
||||
|
||||
let location = LogicalPosition::<f64>::from(position);
|
||||
/// Wrapper around touch to handle release.
|
||||
pub struct Touch {
|
||||
/// Proxy to touch.
|
||||
touch: WlTouch,
|
||||
}
|
||||
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
impl Touch {
|
||||
pub fn new(seat: &Attached<WlSeat>) -> Self {
|
||||
let touch = seat.get_touch();
|
||||
let mut inner = TouchInner::new();
|
||||
|
||||
// Update the state of the point.
|
||||
seat_state
|
||||
.touch_map
|
||||
.insert(id, TouchPoint { surface, location });
|
||||
touch.quick_assign(move |_, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_touch(event, &mut inner, winit_state);
|
||||
});
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Started,
|
||||
location: location.to_physical(scale_factor),
|
||||
force: None,
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
touch: &WlTouch,
|
||||
_: u32,
|
||||
_: u32,
|
||||
id: i32,
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
|
||||
// Remove the touch point.
|
||||
let touch_point = match seat_state.touch_map.remove(&id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Ended,
|
||||
location: touch_point.location.to_physical(scale_factor),
|
||||
force: None,
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
touch: &WlTouch,
|
||||
_: u32,
|
||||
id: i32,
|
||||
position: (f64, f64),
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
|
||||
// Remove the touch point.
|
||||
let touch_point = match seat_state.touch_map.get_mut(&id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
touch_point.location = LogicalPosition::<f64>::from(position);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: touch_point.location.to_physical(scale_factor),
|
||||
force: None,
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Connection, _: &QueueHandle<Self>, touch: &WlTouch) {
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
|
||||
for (id, touch_point) in seat_state.touch_map.drain() {
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let location = touch_point.location.to_physical(scale_factor);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::Touch(Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location,
|
||||
force: None,
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
Self {
|
||||
touch: touch.detach(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: &WlTouch,
|
||||
_: i32,
|
||||
_: f64,
|
||||
_: f64,
|
||||
) {
|
||||
// Blank.
|
||||
}
|
||||
|
||||
fn orientation(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlTouch, _: i32, _: f64) {
|
||||
// Blank.
|
||||
impl Drop for Touch {
|
||||
fn drop(&mut self) {
|
||||
if self.touch.as_ref().version() >= 3 {
|
||||
self.touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the touch point.
|
||||
#[derive(Debug)]
|
||||
pub struct TouchPoint {
|
||||
/// The surface on which the point is present.
|
||||
pub surface: WlSurface,
|
||||
|
||||
/// The location of the point on the surface.
|
||||
pub location: LogicalPosition<f64>,
|
||||
/// The data used by touch handlers.
|
||||
pub(super) struct TouchInner {
|
||||
/// Current touch points.
|
||||
touch_points: Vec<TouchPoint>,
|
||||
}
|
||||
|
||||
pub trait TouchDataExt {
|
||||
fn seat(&self) -> &WlSeat;
|
||||
}
|
||||
|
||||
impl TouchDataExt for WlTouch {
|
||||
fn seat(&self) -> &WlSeat {
|
||||
self.data::<TouchData>()
|
||||
.expect("failed to get touch data.")
|
||||
.seat()
|
||||
impl TouchInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
touch_points: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sctk::delegate_touch!(WinitState);
|
||||
/// Location of touch press.
|
||||
pub(super) struct TouchPoint {
|
||||
/// A surface where the touch point is located.
|
||||
surface: WlSurface,
|
||||
|
||||
/// Location of the touch point.
|
||||
position: LogicalPosition<f64>,
|
||||
|
||||
/// Id.
|
||||
id: i32,
|
||||
}
|
||||
|
||||
impl TouchPoint {
|
||||
pub fn new(surface: WlSurface, position: LogicalPosition<f64>, id: i32) -> Self {
|
||||
Self {
|
||||
surface,
|
||||
position,
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
|
||||
use sctk::reexports::calloop::LoopHandle;
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::globals::GlobalList;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
|
||||
use sctk::compositor::{CompositorHandler, CompositorState};
|
||||
use sctk::output::{OutputHandler, OutputState};
|
||||
use sctk::registry::{ProvidesRegistryState, RegistryState};
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
use sctk::seat::SeatState;
|
||||
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
|
||||
use sctk::shell::xdg::XdgShell;
|
||||
use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::dpi::LogicalSize;
|
||||
|
||||
use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
use super::seat::{
|
||||
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
|
||||
WinitPointerDataExt, WinitSeatState,
|
||||
};
|
||||
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::WindowId;
|
||||
|
||||
/// Winit's Wayland state.
|
||||
pub struct WinitState {
|
||||
/// The WlRegistry.
|
||||
pub registry_state: RegistryState,
|
||||
|
||||
/// The state of the WlOutput handling.
|
||||
pub output_state: OutputState,
|
||||
|
||||
/// The compositor state which is used to create new windows and regions.
|
||||
pub compositor_state: Arc<CompositorState>,
|
||||
|
||||
/// The state of the subcompositor.
|
||||
pub subcompositor_state: Arc<SubcompositorState>,
|
||||
|
||||
/// The seat state responsible for all sorts of input.
|
||||
pub seat_state: SeatState,
|
||||
|
||||
/// The shm for software buffers, such as cursors.
|
||||
pub shm: Shm,
|
||||
|
||||
/// The XDG shell that is used for widnows.
|
||||
pub xdg_shell: XdgShell,
|
||||
|
||||
/// The currently present windows.
|
||||
pub windows: RefCell<FnvHashMap<WindowId, Arc<Mutex<WindowState>>>>,
|
||||
|
||||
/// The requests from the `Window` to EventLoop, such as close operations and redraw requests.
|
||||
pub window_requests: RefCell<FnvHashMap<WindowId, Arc<WindowRequests>>>,
|
||||
|
||||
/// The events that were generated directly from the window.
|
||||
pub window_events_sink: Arc<Mutex<EventSink>>,
|
||||
|
||||
/// The update for the `windows` comming from the compositor.
|
||||
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
|
||||
|
||||
/// Currently handled seats.
|
||||
pub seats: FnvHashMap<ObjectId, WinitSeatState>,
|
||||
|
||||
/// Currently present cursor surfaces.
|
||||
pub pointer_surfaces: FnvHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
/// The state of the text input on the client.
|
||||
pub text_input_state: Option<TextInputState>,
|
||||
|
||||
/// Observed monitors.
|
||||
pub monitors: Arc<Mutex<Vec<MonitorHandle>>>,
|
||||
|
||||
/// Sink to accumulate window events from the compositor, which is latter dispatched in
|
||||
/// event loop run.
|
||||
pub events_sink: EventSink,
|
||||
|
||||
/// Xdg activation.
|
||||
pub xdg_activation: Option<XdgActivationState>,
|
||||
|
||||
/// Relative pointer.
|
||||
pub relative_pointer: Option<RelativePointerState>,
|
||||
|
||||
/// Pointer constraints to handle pointer locking and confining.
|
||||
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
|
||||
|
||||
/// Viewporter state on the given window.
|
||||
pub viewporter_state: Option<ViewporterState>,
|
||||
|
||||
/// Fractional scaling manager.
|
||||
pub fractional_scaling_manager: Option<FractionalScalingManager>,
|
||||
|
||||
/// Loop handle to re-register event sources, such as keyboard repeat.
|
||||
pub loop_handle: LoopHandle<'static, Self>,
|
||||
}
|
||||
|
||||
impl WinitState {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<Self>,
|
||||
loop_handle: LoopHandle<'static, WinitState>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let registry_state = RegistryState::new(globals);
|
||||
let compositor_state = CompositorState::bind(globals, queue_handle)?;
|
||||
let subcompositor_state = SubcompositorState::bind(
|
||||
compositor_state.wl_compositor().clone(),
|
||||
globals,
|
||||
queue_handle,
|
||||
)?;
|
||||
|
||||
let output_state = OutputState::new(globals, queue_handle);
|
||||
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
|
||||
|
||||
let seat_state = SeatState::new(globals, queue_handle);
|
||||
|
||||
let mut seats = FnvHashMap::default();
|
||||
for seat in seat_state.seats() {
|
||||
seats.insert(seat.id(), WinitSeatState::new());
|
||||
}
|
||||
|
||||
let (viewporter_state, fractional_scaling_manager) =
|
||||
if let Ok(fsm) = FractionalScalingManager::new(globals, queue_handle) {
|
||||
(ViewporterState::new(globals, queue_handle).ok(), Some(fsm))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
registry_state,
|
||||
compositor_state: Arc::new(compositor_state),
|
||||
subcompositor_state: Arc::new(subcompositor_state),
|
||||
output_state,
|
||||
seat_state,
|
||||
shm: Shm::bind(globals, queue_handle)?,
|
||||
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle)?,
|
||||
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
|
||||
|
||||
windows: Default::default(),
|
||||
window_requests: Default::default(),
|
||||
window_compositor_updates: Vec::new(),
|
||||
window_events_sink: Default::default(),
|
||||
viewporter_state,
|
||||
fractional_scaling_manager,
|
||||
|
||||
seats,
|
||||
text_input_state: TextInputState::new(globals, queue_handle).ok(),
|
||||
|
||||
relative_pointer: RelativePointerState::new(globals, queue_handle).ok(),
|
||||
pointer_constraints: PointerConstraintsState::new(globals, queue_handle)
|
||||
.map(Arc::new)
|
||||
.ok(),
|
||||
pointer_surfaces: Default::default(),
|
||||
|
||||
monitors: Arc::new(Mutex::new(monitors)),
|
||||
events_sink: EventSink::new(),
|
||||
loop_handle,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn scale_factor_changed(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
scale_factor: f64,
|
||||
is_legacy: bool,
|
||||
) {
|
||||
// Check if the cursor surface.
|
||||
let window_id = super::make_wid(surface);
|
||||
|
||||
if let Some(window) = self.windows.get_mut().get(&window_id) {
|
||||
// Don't update the scaling factor, when legacy method is used.
|
||||
if is_legacy && self.fractional_scaling_manager.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
// The scale factor change is for the window.
|
||||
let pos = if let Some(pos) = self
|
||||
.window_compositor_updates
|
||||
.iter()
|
||||
.position(|update| update.window_id == window_id)
|
||||
{
|
||||
pos
|
||||
} else {
|
||||
self.window_compositor_updates
|
||||
.push(WindowCompositorUpdate::new(window_id));
|
||||
self.window_compositor_updates.len() - 1
|
||||
};
|
||||
|
||||
// Update the scale factor right away.
|
||||
window.lock().unwrap().set_scale_factor(scale_factor);
|
||||
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() {
|
||||
Some(focused_window) => focused_window,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if let Some(window_state) = self.windows.get_mut().get(&focused_window) {
|
||||
window_state.lock().unwrap().reload_cursor_style()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_close(updates: &mut Vec<WindowCompositorUpdate>, window_id: WindowId) {
|
||||
let pos = if let Some(pos) = updates
|
||||
.iter()
|
||||
.position(|update| update.window_id == window_id)
|
||||
{
|
||||
pos
|
||||
} else {
|
||||
updates.push(WindowCompositorUpdate::new(window_id));
|
||||
updates.len() - 1
|
||||
};
|
||||
|
||||
updates[pos].close_window = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmHandler for WinitState {
|
||||
fn shm_state(&mut self) -> &mut Shm {
|
||||
&mut self.shm
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowHandler for WinitState {
|
||||
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, window: &Window) {
|
||||
let window_id = super::make_wid(window.wl_surface());
|
||||
Self::queue_close(&mut self.window_compositor_updates, window_id);
|
||||
}
|
||||
|
||||
fn configure(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
window: &Window,
|
||||
configure: WindowConfigure,
|
||||
_serial: u32,
|
||||
) {
|
||||
let window_id = super::make_wid(window.wl_surface());
|
||||
|
||||
let pos = if let Some(pos) = self
|
||||
.window_compositor_updates
|
||||
.iter()
|
||||
.position(|update| update.window_id == window_id)
|
||||
{
|
||||
pos
|
||||
} else {
|
||||
self.window_compositor_updates
|
||||
.push(WindowCompositorUpdate::new(window_id));
|
||||
self.window_compositor_updates.len() - 1
|
||||
};
|
||||
|
||||
// Populate the configure to the window.
|
||||
//
|
||||
// XXX the size on the window will be updated right before dispatching the size to the user.
|
||||
let new_size = self
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.expect("got configure for dead window.")
|
||||
.lock()
|
||||
.unwrap()
|
||||
.configure(configure, &self.shm, &self.subcompositor_state);
|
||||
|
||||
self.window_compositor_updates[pos].size = Some(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputHandler for WinitState {
|
||||
fn output_state(&mut self) -> &mut OutputState {
|
||||
&mut self.output_state
|
||||
}
|
||||
|
||||
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, output: WlOutput) {
|
||||
self.monitors
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(MonitorHandle::new(output));
|
||||
}
|
||||
|
||||
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, updated: WlOutput) {
|
||||
let mut monitors = self.monitors.lock().unwrap();
|
||||
let updated = MonitorHandle::new(updated);
|
||||
if let Some(pos) = monitors.iter().position(|output| output == &updated) {
|
||||
monitors[pos] = updated
|
||||
} else {
|
||||
monitors.push(updated)
|
||||
}
|
||||
}
|
||||
|
||||
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, removed: WlOutput) {
|
||||
let mut monitors = self.monitors.lock().unwrap();
|
||||
let removed = MonitorHandle::new(removed);
|
||||
if let Some(pos) = monitors.iter().position(|output| output == &removed) {
|
||||
monitors.remove(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompositorHandler for WinitState {
|
||||
fn scale_factor_changed(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
surface: &WlSurface,
|
||||
scale_factor: i32,
|
||||
) {
|
||||
self.scale_factor_changed(surface, scale_factor as f64, true)
|
||||
}
|
||||
|
||||
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: u32) {}
|
||||
}
|
||||
|
||||
impl ProvidesRegistryState for WinitState {
|
||||
fn registry(&mut self) -> &mut RegistryState {
|
||||
&mut self.registry_state
|
||||
}
|
||||
|
||||
sctk::registry_handlers![OutputState, SeatState];
|
||||
}
|
||||
|
||||
// The window update comming from the compositor.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WindowCompositorUpdate {
|
||||
/// The id of the window this updates belongs to.
|
||||
pub window_id: WindowId,
|
||||
|
||||
/// New window size.
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_factor: Option<f64>,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
}
|
||||
|
||||
impl WindowCompositorUpdate {
|
||||
fn new(window_id: WindowId) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
size: None,
|
||||
scale_factor: None,
|
||||
close_window: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sctk::delegate_subcompositor!(WinitState);
|
||||
sctk::delegate_compositor!(WinitState);
|
||||
sctk::delegate_output!(WinitState);
|
||||
sctk::delegate_registry!(WinitState);
|
||||
sctk::delegate_shm!(WinitState);
|
||||
sctk::delegate_xdg_shell!(WinitState);
|
||||
sctk::delegate_xdg_window!(WinitState);
|
||||
@@ -1,5 +0,0 @@
|
||||
//! Wayland protocol implementation boilerplate.
|
||||
|
||||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_viewporter;
|
||||
pub mod xdg_activation;
|
||||
@@ -1,81 +0,0 @@
|
||||
//! Handling of the fractional scaling.
|
||||
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Dispatch;
|
||||
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
||||
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent;
|
||||
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
/// The scaling factor denominator.
|
||||
const SCALE_DENOMINATOR: f64 = 120.;
|
||||
|
||||
/// Fractional scaling manager.
|
||||
#[derive(Debug)]
|
||||
pub struct FractionalScalingManager {
|
||||
manager: WpFractionalScaleManagerV1,
|
||||
}
|
||||
|
||||
pub struct FractionalScaling {
|
||||
/// The surface used for scaling.
|
||||
surface: WlSurface,
|
||||
}
|
||||
|
||||
impl FractionalScalingManager {
|
||||
/// Create new viewporter.
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { manager })
|
||||
}
|
||||
|
||||
pub fn fractional_scaling(
|
||||
&self,
|
||||
surface: &WlSurface,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> WpFractionalScaleV1 {
|
||||
let data = FractionalScaling {
|
||||
surface: surface.clone(),
|
||||
};
|
||||
self.manager
|
||||
.get_fractional_scale(surface, queue_handle, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WpFractionalScaleManagerV1, GlobalData, WinitState> for FractionalScalingManager {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &WpFractionalScaleManagerV1,
|
||||
_: <WpFractionalScaleManagerV1 as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
// No events.
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WpFractionalScaleV1, FractionalScaling, WinitState> for FractionalScalingManager {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
_: &WpFractionalScaleV1,
|
||||
event: <WpFractionalScaleV1 as Proxy>::Event,
|
||||
data: &FractionalScaling,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
if let FractionalScalingEvent::PreferredScale { scale } = event {
|
||||
state.scale_factor_changed(&data.surface, scale as f64 / SCALE_DENOMINATOR, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [WpFractionalScaleManagerV1: GlobalData] => FractionalScalingManager);
|
||||
delegate_dispatch!(WinitState: [WpFractionalScaleV1: FractionalScaling] => FractionalScalingManager);
|
||||
@@ -1,67 +0,0 @@
|
||||
//! Handling of the wp-viewporter.
|
||||
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Dispatch;
|
||||
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
/// Viewporter.
|
||||
#[derive(Debug)]
|
||||
pub struct ViewporterState {
|
||||
viewporter: WpViewporter,
|
||||
}
|
||||
|
||||
impl ViewporterState {
|
||||
/// Create new viewporter.
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let viewporter = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { viewporter })
|
||||
}
|
||||
|
||||
/// Get the viewport for the given object.
|
||||
pub fn get_viewport(
|
||||
&self,
|
||||
surface: &WlSurface,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> WpViewport {
|
||||
self.viewporter
|
||||
.get_viewport(surface, queue_handle, GlobalData)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WpViewporter, GlobalData, WinitState> for ViewporterState {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &WpViewporter,
|
||||
_: <WpViewporter as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
// No events.
|
||||
}
|
||||
}
|
||||
impl Dispatch<WpViewport, GlobalData, WinitState> for ViewporterState {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &WpViewport,
|
||||
_: <WpViewport as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
// No events.
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [WpViewporter: GlobalData] => ViewporterState);
|
||||
delegate_dispatch!(WinitState: [WpViewport: GlobalData] => ViewporterState);
|
||||
@@ -1,103 +0,0 @@
|
||||
//! Handling of xdg activation, which is used for user attention requests.
|
||||
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Weak;
|
||||
|
||||
use sctk::reexports::client::delegate_dispatch;
|
||||
use sctk::reexports::client::globals::BindError;
|
||||
use sctk::reexports::client::globals::GlobalList;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Dispatch;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::{
|
||||
Event as ActivationTokenEvent, XdgActivationTokenV1,
|
||||
};
|
||||
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
||||
pub struct XdgActivationState {
|
||||
xdg_activation: XdgActivationV1,
|
||||
}
|
||||
|
||||
impl XdgActivationState {
|
||||
pub fn bind(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let xdg_activation = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { xdg_activation })
|
||||
}
|
||||
|
||||
pub fn global(&self) -> &XdgActivationV1 {
|
||||
&self.xdg_activation
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgActivationV1, GlobalData, WinitState> for XdgActivationState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &XdgActivationV1,
|
||||
_event: <XdgActivationV1 as Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgActivationState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
proxy: &XdgActivationTokenV1,
|
||||
event: <XdgActivationTokenV1 as Proxy>::Event,
|
||||
data: &XdgActivationTokenData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let token = match event {
|
||||
ActivationTokenEvent::Done { token } => token,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
state
|
||||
.xdg_activation
|
||||
.as_ref()
|
||||
.expect("got xdg_activation event without global.")
|
||||
.global()
|
||||
.activate(token, &data.surface);
|
||||
|
||||
// Mark that no request attention is in process.
|
||||
if let Some(attention_requested) = data.attention_requested.upgrade() {
|
||||
attention_requested.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
proxy.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// The data associated with the activation request.
|
||||
pub struct XdgActivationTokenData {
|
||||
/// The surface we're raising.
|
||||
surface: WlSurface,
|
||||
|
||||
/// Flag to throttle attention requests.
|
||||
attention_requested: Weak<AtomicBool>,
|
||||
}
|
||||
|
||||
impl XdgActivationTokenData {
|
||||
/// Create a new data.
|
||||
///
|
||||
/// The `attenteion_requested` is marked as `false` on complition.
|
||||
pub fn new(surface: WlSurface, attention_requested: Weak<AtomicBool>) -> Self {
|
||||
Self {
|
||||
surface,
|
||||
attention_requested,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ XdgActivationV1: GlobalData] => XdgActivationState);
|
||||
delegate_dispatch!(WinitState: [ XdgActivationTokenV1: XdgActivationTokenData] => XdgActivationState);
|
||||
File diff suppressed because it is too large
Load Diff
666
src/platform_impl/linux/wayland/window/shim.rs
Normal file
666
src/platform_impl/linux/wayland/window/shim.rs
Normal file
@@ -0,0 +1,666 @@
|
||||
use std::cell::Cell;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
|
||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::window::{Decorations, Window};
|
||||
use wayland_protocols::viewporter::client::wp_viewport::WpViewport;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
use crate::event::{Ime, WindowEvent};
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::env::WinitEnv;
|
||||
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
|
||||
use crate::platform_impl::wayland::protocols::wp_fractional_scale_v1::WpFractionalScaleV1;
|
||||
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
|
||||
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, Theme, UserAttentionType};
|
||||
|
||||
use super::WinitFrame;
|
||||
|
||||
/// A request to SCTK window from Winit window.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WindowRequest {
|
||||
/// Set fullscreen.
|
||||
///
|
||||
/// Passing `None` will set it on the current monitor.
|
||||
Fullscreen(Option<WlOutput>),
|
||||
|
||||
/// Unset fullscreen.
|
||||
UnsetFullscreen,
|
||||
|
||||
/// Show cursor for the certain window or not.
|
||||
ShowCursor(bool),
|
||||
|
||||
/// Change the cursor icon.
|
||||
NewCursorIcon(CursorIcon),
|
||||
|
||||
/// Change cursor grabbing mode.
|
||||
SetCursorGrabMode(CursorGrabMode),
|
||||
|
||||
/// Set cursor position.
|
||||
SetLockedCursorPosition(LogicalPosition<u32>),
|
||||
|
||||
/// Drag window.
|
||||
DragWindow,
|
||||
|
||||
/// Maximize the window.
|
||||
Maximize(bool),
|
||||
|
||||
/// Minimize the window.
|
||||
Minimize,
|
||||
|
||||
/// Request decorations change.
|
||||
Decorate(bool),
|
||||
|
||||
/// Make the window resizeable.
|
||||
Resizeable(bool),
|
||||
|
||||
/// Set the title for window.
|
||||
Title(String),
|
||||
|
||||
/// Min size.
|
||||
MinSize(Option<LogicalSize<u32>>),
|
||||
|
||||
/// Max size.
|
||||
MaxSize(Option<LogicalSize<u32>>),
|
||||
|
||||
/// New frame size.
|
||||
FrameSize(LogicalSize<u32>),
|
||||
|
||||
/// Set IME window position.
|
||||
ImePosition(LogicalPosition<u32>),
|
||||
|
||||
/// Enable IME on the given window.
|
||||
AllowIme(bool),
|
||||
|
||||
/// Set the IME purpose.
|
||||
ImePurpose(ImePurpose),
|
||||
|
||||
/// Mark the window as opaque.
|
||||
Transparent(bool),
|
||||
|
||||
/// Request Attention.
|
||||
///
|
||||
/// `None` unsets the attention request.
|
||||
Attention(Option<UserAttentionType>),
|
||||
|
||||
/// Passthrough mouse input to underlying windows.
|
||||
PassthroughMouseInput(bool),
|
||||
|
||||
/// Redraw was requested.
|
||||
Redraw,
|
||||
|
||||
/// Window should be closed.
|
||||
Close,
|
||||
|
||||
/// Change window theme.
|
||||
Theme(Option<Theme>),
|
||||
}
|
||||
|
||||
// The window update comming from the compositor.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct WindowCompositorUpdate {
|
||||
/// New window size.
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_factor: Option<f64>,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
}
|
||||
|
||||
impl WindowCompositorUpdate {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pending update to a window requested by the user.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct WindowUserRequest {
|
||||
/// Whether `redraw` was requested.
|
||||
pub redraw_requested: bool,
|
||||
|
||||
/// Wether the frame should be refreshed.
|
||||
pub refresh_frame: bool,
|
||||
}
|
||||
|
||||
impl WindowUserRequest {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to perform operations on SCTK window
|
||||
/// and react to events.
|
||||
pub struct WindowHandle {
|
||||
/// An actual window.
|
||||
pub window: ManuallyDrop<Window<WinitFrame>>,
|
||||
|
||||
/// The state of the fractional scaling handlers for the window.
|
||||
pub fractional_scaling_state: Option<FractionalScalingState>,
|
||||
|
||||
/// The scale factor of the window.
|
||||
pub scale_factor: Arc<Mutex<f64>>,
|
||||
|
||||
/// The current size of the window.
|
||||
pub size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
|
||||
/// A pending requests to SCTK window.
|
||||
pub pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
|
||||
/// Current cursor icon.
|
||||
pub cursor_icon: Cell<CursorIcon>,
|
||||
|
||||
/// Whether the window is resizable.
|
||||
pub is_resizable: Cell<bool>,
|
||||
|
||||
/// Whether the window has keyboard focus.
|
||||
pub has_focus: Arc<AtomicBool>,
|
||||
|
||||
/// Allow IME events for that window.
|
||||
pub ime_allowed: Cell<bool>,
|
||||
|
||||
/// IME purpose for that window.
|
||||
pub ime_purpose: Cell<ImePurpose>,
|
||||
|
||||
/// Wether the window is transparent.
|
||||
pub transparent: Cell<bool>,
|
||||
|
||||
/// Visible cursor or not.
|
||||
cursor_visible: Cell<bool>,
|
||||
|
||||
/// Cursor confined to the surface.
|
||||
cursor_grab_mode: Cell<CursorGrabMode>,
|
||||
|
||||
/// Pointers over the current surface.
|
||||
pointers: Vec<WinitPointer>,
|
||||
|
||||
/// Text inputs on the current surface.
|
||||
text_inputs: Vec<TextInputHandler>,
|
||||
|
||||
/// XdgActivation object.
|
||||
xdg_activation: Option<Attached<XdgActivationV1>>,
|
||||
|
||||
/// Indicator whether user attention is requested.
|
||||
attention_requested: Cell<bool>,
|
||||
|
||||
/// Compositor
|
||||
compositor: Attached<WlCompositor>,
|
||||
}
|
||||
|
||||
impl WindowHandle {
|
||||
pub fn new(
|
||||
env: &Environment<WinitEnv>,
|
||||
window: Window<WinitFrame>,
|
||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
has_focus: Arc<AtomicBool>,
|
||||
fractional_scaling_state: Option<FractionalScalingState>,
|
||||
scale_factor: f64,
|
||||
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
) -> Self {
|
||||
let xdg_activation = env.get_global::<XdgActivationV1>();
|
||||
// Unwrap is safe, since we can't create window without compositor anyway and won't be
|
||||
// here.
|
||||
let compositor = env.get_global::<WlCompositor>().unwrap();
|
||||
|
||||
Self {
|
||||
window: ManuallyDrop::new(window),
|
||||
fractional_scaling_state,
|
||||
scale_factor: Arc::new(Mutex::new(scale_factor)),
|
||||
size,
|
||||
pending_window_requests,
|
||||
cursor_icon: Cell::new(CursorIcon::Default),
|
||||
is_resizable: Cell::new(true),
|
||||
transparent: Cell::new(false),
|
||||
cursor_grab_mode: Cell::new(CursorGrabMode::None),
|
||||
cursor_visible: Cell::new(true),
|
||||
pointers: Vec::new(),
|
||||
text_inputs: Vec::new(),
|
||||
xdg_activation,
|
||||
attention_requested: Cell::new(false),
|
||||
compositor,
|
||||
ime_allowed: Cell::new(false),
|
||||
ime_purpose: Cell::new(ImePurpose::default()),
|
||||
has_focus,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
*self.scale_factor.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
|
||||
// The new requested state matches the current confine status, return.
|
||||
let old_mode = self.cursor_grab_mode.replace(mode);
|
||||
if old_mode == mode {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear old pointer data.
|
||||
match old_mode {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
|
||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
|
||||
}
|
||||
|
||||
let surface = self.window.surface();
|
||||
match mode {
|
||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
|
||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
|
||||
CursorGrabMode::None => {
|
||||
// Current lock/confine was already removed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
|
||||
// XXX the cursor locking is ensured inside `Window`.
|
||||
self.pointers
|
||||
.iter()
|
||||
.for_each(|p| p.set_cursor_position(position.x, position.y));
|
||||
}
|
||||
|
||||
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
||||
None => return,
|
||||
Some(xdg_activation) => xdg_activation,
|
||||
};
|
||||
|
||||
// Urgency is only removed by the compositor and there's no need to raise urgency when it
|
||||
// was already raised.
|
||||
if request_type.is_none() || self.attention_requested.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
let xdg_activation_token = xdg_activation.get_activation_token();
|
||||
let surface = self.window.surface();
|
||||
let window_id = wayland::make_wid(surface);
|
||||
let xdg_activation = xdg_activation.clone();
|
||||
|
||||
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
|
||||
let token = match event {
|
||||
xdg_activation_token_v1::Event::Done { token } => token,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let surface = window_handle.window.surface();
|
||||
xdg_activation.activate(token, surface);
|
||||
|
||||
// Mark that attention request was done and drop the token.
|
||||
window_handle.attention_requested.replace(false);
|
||||
xdg_token.destroy();
|
||||
});
|
||||
|
||||
xdg_activation_token.set_surface(surface);
|
||||
xdg_activation_token.commit();
|
||||
self.attention_requested.replace(true);
|
||||
}
|
||||
|
||||
/// Pointer appeared over the window.
|
||||
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
|
||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
||||
|
||||
if position.is_none() {
|
||||
let surface = self.window.surface();
|
||||
match self.cursor_grab_mode.get() {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Locked => pointer.lock(surface),
|
||||
CursorGrabMode::Confined => pointer.confine(surface),
|
||||
}
|
||||
|
||||
self.pointers.push(pointer);
|
||||
}
|
||||
|
||||
// Apply the current cursor style.
|
||||
self.set_cursor_visible(self.cursor_visible.get());
|
||||
}
|
||||
|
||||
/// Pointer left the window.
|
||||
pub fn pointer_left(&mut self, pointer: WinitPointer) {
|
||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
||||
|
||||
if let Some(position) = position {
|
||||
let pointer = self.pointers.remove(position);
|
||||
|
||||
// Drop the grabbing mode.
|
||||
match self.cursor_grab_mode.get() {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Locked => pointer.unlock(),
|
||||
CursorGrabMode::Confined => pointer.unconfine(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
|
||||
if !self.text_inputs.iter().any(|t| *t == text_input) {
|
||||
self.text_inputs.push(text_input);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_input_left(&mut self, text_input: TextInputHandler) {
|
||||
if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) {
|
||||
self.text_inputs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_position(&self, position: LogicalPosition<u32>) {
|
||||
// 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);
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_ime_position(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
|
||||
if passthrough_mouse_input {
|
||||
let region = self.compositor.create_region();
|
||||
region.add(0, 0, 0, 0);
|
||||
self.window
|
||||
.surface()
|
||||
.set_input_region(Some(®ion.detach()));
|
||||
region.destroy();
|
||||
} else {
|
||||
// Using `None` results in the entire window being clickable.
|
||||
self.window.surface().set_input_region(None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
self.transparent.set(transparent);
|
||||
let surface = self.window.surface();
|
||||
if transparent {
|
||||
surface.set_opaque_region(None);
|
||||
} else {
|
||||
let region = self.compositor.create_region();
|
||||
region.add(0, 0, i32::MAX, i32::MAX);
|
||||
surface.set_opaque_region(Some(®ion.detach()));
|
||||
region.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
|
||||
if self.ime_allowed.get() == allowed {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ime_allowed.replace(allowed);
|
||||
let window_id = wayland::make_wid(self.window.surface());
|
||||
|
||||
let purpose = allowed.then(|| self.ime_purpose.get());
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_input_allowed(purpose);
|
||||
}
|
||||
|
||||
let event = if allowed {
|
||||
WindowEvent::Ime(Ime::Enabled)
|
||||
} else {
|
||||
WindowEvent::Ime(Ime::Disabled)
|
||||
};
|
||||
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
|
||||
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
|
||||
if self.ime_purpose.get() == purpose {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ime_purpose.replace(purpose);
|
||||
|
||||
if self.ime_allowed.get() {
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_content_type_by_purpose(purpose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.cursor_visible.replace(visible);
|
||||
let cursor_icon = match visible {
|
||||
true => Some(self.cursor_icon.get()),
|
||||
false => None,
|
||||
};
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.set_cursor(cursor_icon)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) {
|
||||
self.cursor_icon.replace(cursor_icon);
|
||||
|
||||
if !self.cursor_visible.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.set_cursor(Some(cursor_icon));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) {
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.drag_window(&self.window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
let window_map = &mut winit_state.window_map;
|
||||
let window_user_requests = &mut winit_state.window_user_requests;
|
||||
let window_compositor_updates = &mut winit_state.window_compositor_updates;
|
||||
let mut windows_to_close: Vec<WindowId> = Vec::new();
|
||||
|
||||
// Process the rest of the events.
|
||||
for (window_id, window_handle) in window_map.iter_mut() {
|
||||
let mut requests = window_handle.pending_window_requests.lock().unwrap();
|
||||
let requests = requests.drain(..);
|
||||
for request in requests {
|
||||
match request {
|
||||
WindowRequest::Fullscreen(fullscreen) => {
|
||||
window_handle.window.set_fullscreen(fullscreen.as_ref());
|
||||
}
|
||||
WindowRequest::UnsetFullscreen => {
|
||||
window_handle.window.unset_fullscreen();
|
||||
}
|
||||
WindowRequest::ShowCursor(show_cursor) => {
|
||||
window_handle.set_cursor_visible(show_cursor);
|
||||
}
|
||||
WindowRequest::NewCursorIcon(cursor_icon) => {
|
||||
window_handle.set_cursor_icon(cursor_icon);
|
||||
}
|
||||
WindowRequest::ImePosition(position) => {
|
||||
window_handle.set_ime_position(position);
|
||||
}
|
||||
WindowRequest::AllowIme(allow) => {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
window_handle.set_ime_allowed(allow, event_sink);
|
||||
}
|
||||
WindowRequest::ImePurpose(purpose) => {
|
||||
window_handle.set_ime_purpose(purpose);
|
||||
}
|
||||
WindowRequest::SetCursorGrabMode(mode) => {
|
||||
window_handle.set_cursor_grab(mode);
|
||||
}
|
||||
WindowRequest::SetLockedCursorPosition(position) => {
|
||||
window_handle.set_locked_cursor_position(position);
|
||||
}
|
||||
WindowRequest::DragWindow => {
|
||||
window_handle.drag_window();
|
||||
}
|
||||
WindowRequest::Maximize(maximize) => {
|
||||
if maximize {
|
||||
window_handle.window.set_maximized();
|
||||
} else {
|
||||
window_handle.window.unset_maximized();
|
||||
}
|
||||
}
|
||||
WindowRequest::Minimize => {
|
||||
window_handle.window.set_minimized();
|
||||
}
|
||||
WindowRequest::Transparent(transparent) => {
|
||||
window_handle.set_transparent(transparent);
|
||||
|
||||
// This requires surface commit.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.redraw_requested = true;
|
||||
}
|
||||
WindowRequest::Decorate(decorate) => {
|
||||
let decorations = match decorate {
|
||||
true => Decorations::FollowServer,
|
||||
false => Decorations::None,
|
||||
};
|
||||
|
||||
window_handle.window.set_decorate(decorations);
|
||||
|
||||
// We should refresh the frame to apply decorations change.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Resizeable(resizeable) => {
|
||||
window_handle.window.set_resizable(resizeable);
|
||||
|
||||
// We should refresh the frame to update button state.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Title(title) => {
|
||||
window_handle.window.set_title(title);
|
||||
|
||||
// We should refresh the frame to draw new title.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::MinSize(size) => {
|
||||
let size = size.map(|size| (size.width, size.height));
|
||||
window_handle.window.set_min_size(size);
|
||||
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::MaxSize(size) => {
|
||||
let size = size.map(|size| (size.width, size.height));
|
||||
window_handle.window.set_max_size(size);
|
||||
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::FrameSize(size) => {
|
||||
if !window_handle.is_resizable.get() {
|
||||
// On Wayland non-resizable window is achieved by setting both min and max
|
||||
// size of the window to the same value.
|
||||
let size = Some((size.width, size.height));
|
||||
window_handle.window.set_max_size(size);
|
||||
window_handle.window.set_min_size(size);
|
||||
}
|
||||
|
||||
window_handle.window.resize(size.width, size.height);
|
||||
|
||||
// We should refresh the frame after resize.
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::PassthroughMouseInput(passthrough) => {
|
||||
window_handle.passthrough_mouse_input(passthrough);
|
||||
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Attention(request_type) => {
|
||||
window_handle.set_user_attention(request_type);
|
||||
}
|
||||
WindowRequest::Redraw => {
|
||||
let window_request = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_request.redraw_requested = true;
|
||||
}
|
||||
WindowRequest::Close => {
|
||||
// The window was requested to be closed.
|
||||
windows_to_close.push(*window_id);
|
||||
|
||||
// Send event that the window was destroyed.
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
event_sink.push_window_event(WindowEvent::Destroyed, *window_id);
|
||||
}
|
||||
WindowRequest::Theme(_theme) => {
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
{
|
||||
window_handle.window.set_frame_config(match _theme {
|
||||
Some(theme) => theme.into(),
|
||||
None => sctk_adwaita::FrameConfig::auto(),
|
||||
});
|
||||
|
||||
let window_requst = window_user_requests.get_mut(window_id).unwrap();
|
||||
window_requst.refresh_frame = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Close the windows.
|
||||
for window in windows_to_close {
|
||||
let _ = window_map.remove(&window);
|
||||
let _ = window_user_requests.remove(&window);
|
||||
let _ = window_compositor_updates.remove(&window);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowHandle {
|
||||
fn drop(&mut self) {
|
||||
// Drop the fractional scaling before the surface.
|
||||
let _ = self.fractional_scaling_state.take();
|
||||
|
||||
unsafe {
|
||||
let surface = self.window.surface().clone();
|
||||
// The window must be destroyed before wl_surface.
|
||||
ManuallyDrop::drop(&mut self.window);
|
||||
surface.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fractional scaling objects.
|
||||
pub struct FractionalScalingState {
|
||||
/// The wp-viewport of the window.
|
||||
pub viewport: WpViewport,
|
||||
|
||||
/// The wp-fractional-scale of the window surface.
|
||||
pub fractional_scale: WpFractionalScaleV1,
|
||||
}
|
||||
|
||||
impl FractionalScalingState {
|
||||
pub fn new(viewport: WpViewport, fractional_scale: WpFractionalScaleV1) -> Self {
|
||||
Self {
|
||||
viewport,
|
||||
fractional_scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FractionalScalingState {
|
||||
fn drop(&mut self) {
|
||||
self.viewport.destroy();
|
||||
self.fractional_scale.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,895 +0,0 @@
|
||||
//! The state of the window, which is shared with the event-loop.
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use log::warn;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge;
|
||||
|
||||
use sctk::compositor::{CompositorState, Region, SurfaceData};
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
use sctk::shell::xdg::frame::{DecorationsFrame, FrameAction, FrameClick};
|
||||
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
|
||||
use sctk::shell::xdg::XdgSurface;
|
||||
use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::Shm;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
|
||||
|
||||
use crate::platform_impl::wayland::seat::{
|
||||
PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
|
||||
};
|
||||
use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
|
||||
#[cfg(not(feature = "sctk-adwaita"))]
|
||||
pub type WinitFrame = sctk::shell::xdg::frame::fallback_frame::FallbackFrame<WinitState>;
|
||||
|
||||
// Minimum window inner size.
|
||||
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
|
||||
|
||||
/// The state of the window which is being updated from the [`WinitState`].
|
||||
pub struct WindowState {
|
||||
/// The connection to Wayland server.
|
||||
pub connection: Connection,
|
||||
|
||||
/// The underlying SCTK window.
|
||||
pub window: ManuallyDrop<Window>,
|
||||
|
||||
/// The window frame, which is created from the configure request.
|
||||
frame: Option<WinitFrame>,
|
||||
|
||||
/// The `Shm` to set cursor.
|
||||
pub shm: WlShm,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Wether the cursor is visible.
|
||||
pub cursor_visible: bool,
|
||||
|
||||
/// Pointer constraints to lock/confine pointer.
|
||||
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
|
||||
|
||||
/// Queue handle.
|
||||
pub queue_handle: QueueHandle<WinitState>,
|
||||
|
||||
/// Theme varaint.
|
||||
theme: Option<Theme>,
|
||||
|
||||
/// The current window title.
|
||||
title: String,
|
||||
|
||||
/// Whether the frame is resizable.
|
||||
resizable: bool,
|
||||
|
||||
/// Whether the window has focus.
|
||||
has_focus: bool,
|
||||
|
||||
/// The scale factor of the window.
|
||||
scale_factor: f64,
|
||||
|
||||
/// Whether the window is transparent.
|
||||
transparent: bool,
|
||||
|
||||
/// The state of the compositor to create WlRegions.
|
||||
compositor: Arc<CompositorState>,
|
||||
|
||||
/// The current cursor grabbing mode.
|
||||
cursor_grab_mode: GrabState,
|
||||
|
||||
/// Whether the IME input is allowed for that window.
|
||||
ime_allowed: bool,
|
||||
|
||||
/// The current IME purpose.
|
||||
ime_purpose: ImePurpose,
|
||||
|
||||
/// The text inputs observed on the window.
|
||||
text_inputs: Vec<ZwpTextInputV3>,
|
||||
|
||||
/// The inner size of the window, as in without client side decorations.
|
||||
size: LogicalSize<u32>,
|
||||
|
||||
/// Whether the CSD fail to create, so we don't try to create them on each iteration.
|
||||
csd_fails: bool,
|
||||
|
||||
/// Whether we should decorate the frame.
|
||||
decorate: bool,
|
||||
|
||||
/// Min size.
|
||||
min_inner_size: LogicalSize<u32>,
|
||||
max_inner_size: Option<LogicalSize<u32>>,
|
||||
|
||||
/// The size of the window when no states were applied to it. The primary use for it
|
||||
/// is to fallback to original window size, before it was maximized, if the compositor
|
||||
/// sends `None` for the new size in the configure.
|
||||
stateless_size: LogicalSize<u32>,
|
||||
|
||||
viewport: Option<WpViewport>,
|
||||
fractional_scale: Option<WpFractionalScaleV1>,
|
||||
}
|
||||
|
||||
/// The state of the cursor grabs.
|
||||
#[derive(Clone, Copy)]
|
||||
struct GrabState {
|
||||
/// The grab mode requested by the user.
|
||||
user_grab_mode: CursorGrabMode,
|
||||
|
||||
/// The current grab mode.
|
||||
current_grab_mode: CursorGrabMode,
|
||||
}
|
||||
|
||||
impl GrabState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
user_grab_mode: CursorGrabMode::None,
|
||||
current_grab_mode: CursorGrabMode::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
/// Apply closure on the given pointer.
|
||||
fn apply_on_poiner<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
|
||||
&self,
|
||||
callback: F,
|
||||
) {
|
||||
self.pointers
|
||||
.iter()
|
||||
.filter_map(Weak::upgrade)
|
||||
.for_each(|pointer| {
|
||||
let data = pointer.pointer().winit_data();
|
||||
callback(pointer.as_ref(), data);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn configure(
|
||||
&mut self,
|
||||
configure: WindowConfigure,
|
||||
shm: &Shm,
|
||||
subcompositor: &Arc<SubcompositorState>,
|
||||
) -> LogicalSize<u32> {
|
||||
if configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
{
|
||||
match WinitFrame::new(
|
||||
&*self.window,
|
||||
shm,
|
||||
subcompositor.clone(),
|
||||
self.queue_handle.clone(),
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
into_sctk_adwaita_config(self.theme),
|
||||
) {
|
||||
Ok(mut frame) => {
|
||||
frame.set_title(&self.title);
|
||||
// Hide the frame if we were asked to not decorate.
|
||||
frame.set_hidden(!self.decorate);
|
||||
self.frame = Some(frame);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Failed to create client side decorations frame: {err}");
|
||||
self.csd_fails = true;
|
||||
}
|
||||
}
|
||||
} else if configure.decoration_mode == DecorationMode::Server {
|
||||
// Drop the frame for server side decorations to save resources.
|
||||
self.frame = None;
|
||||
}
|
||||
|
||||
let stateless = Self::is_stateless(&configure);
|
||||
|
||||
let new_size = if let Some(frame) = self.frame.as_mut() {
|
||||
// Configure the window states.
|
||||
frame.update_state(configure.state);
|
||||
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => {
|
||||
let (width, height) = frame.subtract_borders(width, height);
|
||||
(
|
||||
width.map(|w| w.get()).unwrap_or(1),
|
||||
height.map(|h| h.get()).unwrap_or(1),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
(_, _) if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
}
|
||||
} else {
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => (width.get(), height.get()).into(),
|
||||
_ if stateless => self.stateless_size,
|
||||
_ => self.size,
|
||||
}
|
||||
};
|
||||
|
||||
// XXX Set the configure before doing a resize.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
// XXX Update the new size right away.
|
||||
self.resize(new_size);
|
||||
|
||||
new_size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_stateless(configure: &WindowConfigure) -> bool {
|
||||
!(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
|
||||
}
|
||||
|
||||
/// Start interacting drag resize.
|
||||
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
|
||||
let xdg_toplevel = self.window.xdg_toplevel();
|
||||
|
||||
// TODO(kchibisov) handle touch serials.
|
||||
self.apply_on_poiner(|_, data| {
|
||||
let serial = data.latest_button_serial();
|
||||
let seat = data.seat();
|
||||
xdg_toplevel.resize(seat, serial, direction.into());
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the window drag.
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
let xdg_toplevel = self.window.xdg_toplevel();
|
||||
// TODO(kchibisov) handle touch serials.
|
||||
self.apply_on_poiner(|_, data| {
|
||||
let serial = data.latest_button_serial();
|
||||
let seat = data.seat();
|
||||
xdg_toplevel._move(seat, serial);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tells whether the window should be closed.
|
||||
pub fn frame_click(
|
||||
&mut self,
|
||||
click: FrameClick,
|
||||
pressed: bool,
|
||||
seat: &WlSeat,
|
||||
serial: u32,
|
||||
window_id: WindowId,
|
||||
updates: &mut Vec<WindowCompositorUpdate>,
|
||||
) -> Option<bool> {
|
||||
match self.frame.as_mut()?.on_click(click, pressed)? {
|
||||
FrameAction::Minimize => self.window.set_minimized(),
|
||||
FrameAction::Maximize => self.window.set_maximized(),
|
||||
FrameAction::UnMaximize => self.window.unset_maximized(),
|
||||
FrameAction::Close => WinitState::queue_close(updates, window_id),
|
||||
FrameAction::Move => self.window.move_(seat, serial),
|
||||
FrameAction::Resize(edge) => self.window.resize(seat, serial, edge),
|
||||
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
|
||||
};
|
||||
|
||||
Some(false)
|
||||
}
|
||||
|
||||
pub fn frame_point_left(&mut self) {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.click_point_left();
|
||||
}
|
||||
}
|
||||
|
||||
// Move the point over decorations.
|
||||
pub fn frame_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str> {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.click_point_moved(surface, x, y)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the stored resizable state.
|
||||
#[inline]
|
||||
pub fn resizable(&self) -> bool {
|
||||
self.resizable
|
||||
}
|
||||
|
||||
/// Set the resizable state on the window.
|
||||
#[inline]
|
||||
pub fn set_resizable(&mut self, resizable: bool) {
|
||||
if self.resizable == resizable {
|
||||
return;
|
||||
}
|
||||
|
||||
self.resizable = resizable;
|
||||
if resizable {
|
||||
// Restore min/max sizes of the window.
|
||||
self.reload_min_max_hints();
|
||||
} else {
|
||||
self.set_min_inner_size(Some(self.size));
|
||||
self.set_max_inner_size(Some(self.size));
|
||||
}
|
||||
|
||||
// Reload the state on the frame as well.
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_resizable(resizable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the window is focused.
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.has_focus
|
||||
}
|
||||
|
||||
/// Whether the IME is allowed.
|
||||
#[inline]
|
||||
pub fn ime_allowed(&self) -> bool {
|
||||
self.ime_allowed
|
||||
}
|
||||
|
||||
/// Get the size of the window.
|
||||
#[inline]
|
||||
pub fn inner_size(&self) -> LogicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Whether the window received initial configure event from the compositor.
|
||||
#[inline]
|
||||
pub fn is_configured(&self) -> bool {
|
||||
self.last_configure.is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_decorated(&mut self) -> bool {
|
||||
let csd = self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|configure| configure.decoration_mode == DecorationMode::Client)
|
||||
.unwrap_or(false);
|
||||
if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
|
||||
frame.is_hidden()
|
||||
} else {
|
||||
// Server side decorations.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new window state.
|
||||
pub fn new(
|
||||
connection: Connection,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
winit_state: &WinitState,
|
||||
size: LogicalSize<u32>,
|
||||
window: Window,
|
||||
theme: Option<Theme>,
|
||||
) -> Self {
|
||||
let compositor = winit_state.compositor_state.clone();
|
||||
let pointer_constraints = winit_state.pointer_constraints.clone();
|
||||
let viewport = winit_state
|
||||
.viewporter_state
|
||||
.as_ref()
|
||||
.map(|state| state.get_viewport(window.wl_surface(), queue_handle));
|
||||
let fractional_scale = winit_state
|
||||
.fractional_scaling_manager
|
||||
.as_ref()
|
||||
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
|
||||
|
||||
Self {
|
||||
compositor,
|
||||
connection,
|
||||
theme,
|
||||
csd_fails: false,
|
||||
decorate: true,
|
||||
cursor_grab_mode: GrabState::new(),
|
||||
cursor_icon: CursorIcon::Default,
|
||||
cursor_visible: true,
|
||||
fractional_scale,
|
||||
frame: None,
|
||||
has_focus: false,
|
||||
ime_allowed: false,
|
||||
ime_purpose: ImePurpose::Normal,
|
||||
last_configure: None,
|
||||
max_inner_size: None,
|
||||
min_inner_size: MIN_WINDOW_SIZE,
|
||||
pointer_constraints,
|
||||
pointers: Default::default(),
|
||||
queue_handle: queue_handle.clone(),
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
size,
|
||||
stateless_size: size,
|
||||
text_inputs: Vec::new(),
|
||||
title: String::default(),
|
||||
transparent: false,
|
||||
resizable: true,
|
||||
viewport,
|
||||
window: ManuallyDrop::new(window),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the outer size of the window.
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> LogicalSize<u32> {
|
||||
self.frame
|
||||
.as_ref()
|
||||
.map(|frame| frame.add_borders(self.size.width, self.size.height).into())
|
||||
.unwrap_or(self.size)
|
||||
}
|
||||
|
||||
/// Register pointer on the top-level.
|
||||
pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
|
||||
self.pointers.push(added);
|
||||
self.reload_cursor_style();
|
||||
|
||||
let mode = self.cursor_grab_mode.user_grab_mode;
|
||||
let _ = self.set_cursor_grab_inner(mode);
|
||||
}
|
||||
|
||||
/// Pointer has left the top-level.
|
||||
pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
|
||||
let mut new_pointers = Vec::new();
|
||||
for pointer in self.pointers.drain(..) {
|
||||
if let Some(pointer) = pointer.upgrade() {
|
||||
if pointer.pointer() != removed.upgrade().unwrap().pointer() {
|
||||
new_pointers.push(Arc::downgrade(&pointer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.pointers = new_pointers;
|
||||
}
|
||||
|
||||
/// Refresh the decorations frame if it's present returning whether the client should redraw.
|
||||
pub fn refresh_frame(&mut self) -> bool {
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
let dirty = frame.is_dirty();
|
||||
if dirty {
|
||||
frame.draw();
|
||||
}
|
||||
dirty
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
} else {
|
||||
self.set_cursor_visible(self.cursor_visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reissue the transparency hint to the compositor.
|
||||
pub fn reload_transparency_hint(&self) {
|
||||
let surface = self.window.wl_surface();
|
||||
|
||||
if self.transparent {
|
||||
surface.set_opaque_region(None);
|
||||
} else if let Ok(region) = Region::new(&*self.compositor) {
|
||||
region.add(0, 0, i32::MAX, i32::MAX);
|
||||
surface.set_opaque_region(Some(region.wl_region()));
|
||||
} else {
|
||||
warn!("Failed to mark window opaque.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the window to the new inner size.
|
||||
pub fn resize(&mut self, inner_size: LogicalSize<u32>) {
|
||||
self.size = inner_size;
|
||||
|
||||
// Update the stateless size.
|
||||
if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
|
||||
self.stateless_size = inner_size;
|
||||
}
|
||||
|
||||
// Update the inner frame.
|
||||
let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
|
||||
// Resize only visible frame.
|
||||
if !frame.is_hidden() {
|
||||
frame.resize(
|
||||
NonZeroU32::new(self.size.width).unwrap(),
|
||||
NonZeroU32::new(self.size.height).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
(
|
||||
frame.location(),
|
||||
frame.add_borders(self.size.width, self.size.height).into(),
|
||||
)
|
||||
} else {
|
||||
((0, 0), self.size)
|
||||
};
|
||||
|
||||
// Reload the hint.
|
||||
self.reload_transparency_hint();
|
||||
|
||||
// Set the window geometry.
|
||||
self.window.xdg_surface().set_window_geometry(
|
||||
x,
|
||||
y,
|
||||
outer_size.width as i32,
|
||||
outer_size.height as i32,
|
||||
);
|
||||
|
||||
// Update the target viewport, this is used if and only if fractional scaling is in use.
|
||||
if let Some(viewport) = self.viewport.as_ref() {
|
||||
// Set inner size without the borders.
|
||||
viewport.set_destination(self.size.width as _, self.size.height as _);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the scale factor of the window.
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.scale_factor
|
||||
}
|
||||
|
||||
/// Set the cursor icon.
|
||||
///
|
||||
/// Providing `None` will hide the cursor.
|
||||
pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
|
||||
self.cursor_icon = cursor_icon;
|
||||
|
||||
if !self.cursor_visible {
|
||||
return;
|
||||
}
|
||||
|
||||
self.apply_on_poiner(|pointer, data| {
|
||||
let surface = data.cursor_surface();
|
||||
let scale_factor = surface.data::<SurfaceData>().unwrap().scale_factor();
|
||||
|
||||
if pointer
|
||||
.set_cursor(
|
||||
&self.connection,
|
||||
cursor_icon.name(),
|
||||
&self.shm,
|
||||
surface,
|
||||
scale_factor,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
warn!("Failed to set cursor to {:?}", cursor_icon);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
|
||||
size.width = size.width.max(MIN_WINDOW_SIZE.width);
|
||||
size.height = size.height.max(MIN_WINDOW_SIZE.height);
|
||||
|
||||
// Add the borders.
|
||||
let size = self
|
||||
.frame
|
||||
.as_ref()
|
||||
.map(|frame| frame.add_borders(size.width, size.height).into())
|
||||
.unwrap_or(size);
|
||||
|
||||
self.min_inner_size = size;
|
||||
self.window.set_min_size(Some(size.into()));
|
||||
}
|
||||
|
||||
/// Set maximum inner window size.
|
||||
pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
let size = size.map(|size| {
|
||||
self.frame
|
||||
.as_ref()
|
||||
.map(|frame| frame.add_borders(size.width, size.height).into())
|
||||
.unwrap_or(size)
|
||||
});
|
||||
|
||||
self.max_inner_size = size;
|
||||
self.window.set_max_size(size.map(Into::into));
|
||||
}
|
||||
|
||||
/// Set the CSD theme.
|
||||
pub fn set_theme(&mut self, theme: Option<Theme>) {
|
||||
self.theme = theme;
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_config(into_sctk_adwaita_config(theme))
|
||||
}
|
||||
}
|
||||
|
||||
/// The current theme for CSD decorations.
|
||||
#[inline]
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
self.theme
|
||||
}
|
||||
|
||||
/// Set the cursor grabbing state on the top-level.
|
||||
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
// Replace the user grabbing mode.
|
||||
self.cursor_grab_mode.user_grab_mode = mode;
|
||||
self.set_cursor_grab_inner(mode)
|
||||
}
|
||||
|
||||
/// Reload the hints for minimum and maximum sizes.
|
||||
pub fn reload_min_max_hints(&mut self) {
|
||||
self.set_min_inner_size(Some(self.min_inner_size));
|
||||
self.set_max_inner_size(self.max_inner_size);
|
||||
}
|
||||
|
||||
/// Set the grabbing state on the surface.
|
||||
fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
||||
let pointer_constraints = match self.pointer_constraints.as_ref() {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None if mode == CursorGrabMode::None => return Ok(()),
|
||||
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
|
||||
};
|
||||
|
||||
// Replace the current mode.
|
||||
let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
|
||||
|
||||
match old_mode {
|
||||
CursorGrabMode::None => (),
|
||||
CursorGrabMode::Confined => self.apply_on_poiner(|_, data| {
|
||||
data.unconfine_pointer();
|
||||
}),
|
||||
CursorGrabMode::Locked => {
|
||||
self.apply_on_poiner(|_, data| data.unlock_pointer());
|
||||
}
|
||||
}
|
||||
|
||||
let surface = self.window.wl_surface();
|
||||
match mode {
|
||||
CursorGrabMode::Locked => self.apply_on_poiner(|pointer, data| {
|
||||
let pointer = pointer.pointer();
|
||||
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
|
||||
}),
|
||||
CursorGrabMode::Confined => self.apply_on_poiner(|pointer, data| {
|
||||
let pointer = pointer.pointer();
|
||||
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
|
||||
}),
|
||||
CursorGrabMode::None => {
|
||||
// Current lock/confine was already removed.
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the position of the cursor.
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
|
||||
if self.pointer_constraints.is_none() {
|
||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
||||
}
|
||||
|
||||
// Positon can be set only for locked cursor.
|
||||
if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
|
||||
return Err(ExternalError::Os(os_error!(
|
||||
crate::platform_impl::OsError::WaylandMisc(
|
||||
"cursor position can be set only for locked cursor."
|
||||
)
|
||||
)));
|
||||
}
|
||||
|
||||
self.apply_on_poiner(|_, data| {
|
||||
data.set_locked_cursor_position(position.x, position.y);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the visibility state of the cursor.
|
||||
pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
|
||||
self.cursor_visible = cursor_visible;
|
||||
|
||||
if self.cursor_visible {
|
||||
self.set_cursor(self.cursor_icon);
|
||||
} else {
|
||||
for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
|
||||
let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
|
||||
|
||||
pointer
|
||||
.pointer()
|
||||
.set_cursor(latest_enter_serial, None, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether show or hide client side decorations.
|
||||
#[inline]
|
||||
pub fn set_decorate(&mut self, decorate: bool) {
|
||||
if decorate == self.decorate {
|
||||
return;
|
||||
}
|
||||
|
||||
self.decorate = decorate;
|
||||
|
||||
match self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|configure| configure.decoration_mode)
|
||||
{
|
||||
Some(DecorationMode::Server) if !self.decorate => {
|
||||
// To disable decorations we should request client and hide the frame.
|
||||
self.window
|
||||
.request_decoration_mode(Some(DecorationMode::Client))
|
||||
}
|
||||
_ if self.decorate => self
|
||||
.window
|
||||
.request_decoration_mode(Some(DecorationMode::Server)),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_hidden(!decorate);
|
||||
// Force the resize.
|
||||
self.resize(self.size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark that the window has focus.
|
||||
///
|
||||
/// Should be used from routine that sends focused event.
|
||||
#[inline]
|
||||
pub fn set_has_focus(&mut self, has_focus: bool) {
|
||||
self.has_focus = has_focus;
|
||||
}
|
||||
|
||||
/// Returns `true` if the requested state was applied.
|
||||
pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
|
||||
self.ime_allowed = allowed;
|
||||
|
||||
let mut applied = false;
|
||||
for text_input in &self.text_inputs {
|
||||
applied = true;
|
||||
if allowed {
|
||||
text_input.enable();
|
||||
text_input.set_content_type_by_purpose(self.ime_purpose);
|
||||
} else {
|
||||
text_input.disable();
|
||||
}
|
||||
text_input.commit();
|
||||
}
|
||||
|
||||
applied
|
||||
}
|
||||
|
||||
/// Set the IME position.
|
||||
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// 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);
|
||||
let (width, height) = (size.width as i32, size.height as i32);
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_cursor_rectangle(x, y, width, height);
|
||||
text_input.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the IME purpose.
|
||||
pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
|
||||
self.ime_purpose = purpose;
|
||||
|
||||
for text_input in &self.text_inputs {
|
||||
text_input.set_content_type_by_purpose(purpose);
|
||||
text_input.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the IME purpose.
|
||||
pub fn ime_purpose(&self) -> ImePurpose {
|
||||
self.ime_purpose
|
||||
}
|
||||
|
||||
/// Set the scale factor for the given window.
|
||||
#[inline]
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f64) {
|
||||
self.scale_factor = scale_factor;
|
||||
|
||||
// XXX when fractional scaling is not used update the buffer scale.
|
||||
if self.fractional_scale.is_none() {
|
||||
let _ = self.window.set_buffer_scale(self.scale_factor as _);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the window title to a new value.
|
||||
///
|
||||
/// This will autmatically truncate the title to something meaningfull.
|
||||
pub fn set_title(&mut self, mut title: String) {
|
||||
// Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
|
||||
// messages
|
||||
if title.len() > 1024 {
|
||||
let mut new_len = 1024;
|
||||
while !title.is_char_boundary(new_len) {
|
||||
new_len -= 1;
|
||||
}
|
||||
title.truncate(new_len);
|
||||
}
|
||||
|
||||
// Update the CSD title.
|
||||
if let Some(frame) = self.frame.as_mut() {
|
||||
frame.set_title(&title);
|
||||
}
|
||||
|
||||
self.window.set_title(&title);
|
||||
self.title = title;
|
||||
}
|
||||
|
||||
/// Mark the window as transparent.
|
||||
#[inline]
|
||||
pub fn set_transparent(&mut self, transparent: bool) {
|
||||
self.transparent = transparent;
|
||||
self.reload_transparency_hint();
|
||||
}
|
||||
|
||||
/// Register text input on the top-level.
|
||||
#[inline]
|
||||
pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
|
||||
if !self.text_inputs.iter().any(|t| t == text_input) {
|
||||
self.text_inputs.push(text_input.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// The text input left the top-level.
|
||||
#[inline]
|
||||
pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
|
||||
if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
|
||||
self.text_inputs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the cached title.
|
||||
#[inline]
|
||||
pub fn title(&self) -> &str {
|
||||
&self.title
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowState {
|
||||
fn drop(&mut self) {
|
||||
let surface = self.window.wl_surface().clone();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.window);
|
||||
}
|
||||
|
||||
surface.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResizeDirection> for ResizeEdge {
|
||||
fn from(value: ResizeDirection) -> Self {
|
||||
match value {
|
||||
ResizeDirection::North => ResizeEdge::Top,
|
||||
ResizeDirection::West => ResizeEdge::Left,
|
||||
ResizeDirection::NorthWest => ResizeEdge::TopLeft,
|
||||
ResizeDirection::NorthEast => ResizeEdge::TopRight,
|
||||
ResizeDirection::East => ResizeEdge::Right,
|
||||
ResizeDirection::SouthWest => ResizeEdge::BottomLeft,
|
||||
ResizeDirection::SouthEast => ResizeEdge::BottomRight,
|
||||
ResizeDirection::South => ResizeEdge::Bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXX rust doesn't allow from `Option`.
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
|
||||
match theme {
|
||||
Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
|
||||
Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
|
||||
None => sctk_adwaita::FrameConfig::auto(),
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,23 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
|
||||
|
||||
use libc::{c_char, c_int, c_long, c_ulong};
|
||||
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
|
||||
|
||||
use super::{
|
||||
ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, DndState,
|
||||
GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, XExtension,
|
||||
events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd,
|
||||
DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
|
||||
XExtension,
|
||||
};
|
||||
|
||||
use util::modifiers::{ModifierKeyState, ModifierKeymap};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::{DeviceEvent, ElementState, Event, Ime, RawKeyEvent, TouchPhase, WindowEvent},
|
||||
event::{
|
||||
DeviceEvent, ElementState, Event, Ime, KeyboardInput, ModifiersState, TouchPhase,
|
||||
WindowEvent,
|
||||
},
|
||||
event_loop::EventLoopWindowTarget as RootELW,
|
||||
keyboard::ModifiersState,
|
||||
platform_impl::platform::common::{keymap, xkb_state::KbdState},
|
||||
};
|
||||
|
||||
/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`".
|
||||
@@ -26,13 +30,11 @@ pub(super) struct EventProcessor<T: 'static> {
|
||||
pub(super) randr_event_offset: c_int,
|
||||
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
|
||||
pub(super) xi2ext: XExtension,
|
||||
pub(super) xkbext: XExtension,
|
||||
pub(super) target: Rc<RootELW<T>>,
|
||||
pub(super) kb_state: KbdState,
|
||||
pub(super) mod_keymap: ModifierKeymap,
|
||||
pub(super) device_mod_state: ModifierKeyState,
|
||||
// Number of touch events currently in progress
|
||||
pub(super) num_touch: u32,
|
||||
// Whether we've got a key release for the key press.
|
||||
pub(super) got_key_release: bool,
|
||||
pub(super) first_touch: Option<u64>,
|
||||
// Currently focused window belonging to this process
|
||||
pub(super) active_window: Option<ffi::Window>,
|
||||
@@ -132,8 +134,47 @@ impl<T: 'static> EventProcessor<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't call a `&mut self` method because of the above borrow,
|
||||
// so we use this macro for repeated modifier state updates.
|
||||
macro_rules! update_modifiers {
|
||||
( $state:expr , $modifier:expr ) => {{
|
||||
match ($state, $modifier) {
|
||||
(state, modifier) => {
|
||||
if let Some(modifiers) =
|
||||
self.device_mod_state.update_state(&state, modifier)
|
||||
{
|
||||
if let Some(window_id) = self.active_window {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window_id),
|
||||
event: WindowEvent::ModifiersChanged(modifiers),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
let event_type = xev.get_type();
|
||||
match event_type {
|
||||
ffi::MappingNotify => {
|
||||
let mapping: &ffi::XMappingEvent = xev.as_ref();
|
||||
|
||||
if mapping.request == ffi::MappingModifier
|
||||
|| mapping.request == ffi::MappingKeyboard
|
||||
{
|
||||
unsafe {
|
||||
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
|
||||
}
|
||||
wt.xconn
|
||||
.check_errors()
|
||||
.expect("Failed to call XRefreshKeyboardMapping");
|
||||
|
||||
self.mod_keymap.reset_from_x_connection(&wt.xconn);
|
||||
self.device_mod_state.update_keymap(&self.mod_keymap);
|
||||
}
|
||||
}
|
||||
|
||||
ffi::ClientMessage => {
|
||||
let client_msg: &ffi::XClientMessageEvent = xev.as_ref();
|
||||
|
||||
@@ -536,41 +577,67 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Note that in compose/pre-edit sequences, we'll always receive KeyRelease events
|
||||
ty @ ffi::KeyPress | ty @ ffi::KeyRelease => {
|
||||
let xkev: &mut ffi::XKeyEvent = xev.as_mut();
|
||||
let window = match self.active_window {
|
||||
Some(window) => window,
|
||||
None => return,
|
||||
};
|
||||
ffi::KeyPress | ffi::KeyRelease => {
|
||||
use crate::event::ElementState::{Pressed, Released};
|
||||
|
||||
let window_id = mkwid(window);
|
||||
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
|
||||
|
||||
let keycode = xkev.keycode as _;
|
||||
let repeat = ty == ffi::KeyPress && !self.got_key_release;
|
||||
// Update state after the repeat setting.
|
||||
let state = if ty == ffi::KeyPress {
|
||||
self.got_key_release = false;
|
||||
ElementState::Pressed
|
||||
// Note that in compose/pre-edit sequences, this will always be Released.
|
||||
let state = if xev.get_type() == ffi::KeyPress {
|
||||
Pressed
|
||||
} else {
|
||||
self.got_key_release = true;
|
||||
ElementState::Released
|
||||
Released
|
||||
};
|
||||
|
||||
let xkev: &mut ffi::XKeyEvent = xev.as_mut();
|
||||
|
||||
let window = xkev.window;
|
||||
let window_id = mkwid(window);
|
||||
|
||||
// Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable
|
||||
// value, though this should only be an issue under multiseat configurations.
|
||||
let device = util::VIRTUAL_CORE_KEYBOARD;
|
||||
let device_id = mkdid(device);
|
||||
let keycode = xkev.keycode;
|
||||
|
||||
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
|
||||
// a keycode of 0.
|
||||
if keycode != 0 && !self.is_composing {
|
||||
let event = self.kb_state.process_key_event(keycode, state, repeat);
|
||||
let scancode = keycode - KEYCODE_OFFSET as u32;
|
||||
let keysym = wt.xconn.lookup_keysym(xkev);
|
||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
||||
|
||||
update_modifiers!(
|
||||
ModifiersState::from_x11_mask(xkev.state),
|
||||
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
|
||||
);
|
||||
|
||||
let modifiers = self.device_mod_state.modifiers();
|
||||
|
||||
#[allow(deprecated)]
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode,
|
||||
virtual_keycode,
|
||||
modifiers,
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
});
|
||||
} else if let Some(ic) = wt.ime.borrow().get_context(window) {
|
||||
let written = wt.xconn.lookup_utf8(ic, xkev);
|
||||
if !written.is_empty() {
|
||||
}
|
||||
|
||||
if state == Pressed {
|
||||
let written = if let Some(ic) = wt.ime.borrow().get_context(window) {
|
||||
wt.xconn.lookup_utf8(ic, xkev)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If we're composing right now, send the string we've got from X11 via
|
||||
// Ime::Commit.
|
||||
if self.is_composing && keycode == 0 && !written.is_empty() {
|
||||
let event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
@@ -584,6 +651,15 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
self.is_composing = false;
|
||||
callback(event);
|
||||
} else {
|
||||
for chr in written.chars() {
|
||||
let event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ReceivedCharacter(chr),
|
||||
};
|
||||
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -601,7 +677,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
use crate::event::{
|
||||
ElementState::{Pressed, Released},
|
||||
MouseButton::{Back, Forward, Left, Middle, Other, Right},
|
||||
MouseButton::{Left, Middle, Other, Right},
|
||||
MouseScrollDelta::LineDelta,
|
||||
Touch,
|
||||
WindowEvent::{
|
||||
@@ -620,6 +696,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
let modifiers = ModifiersState::from_x11(&xev.mods);
|
||||
update_modifiers!(modifiers, None);
|
||||
|
||||
let state = if xev.evtype == ffi::XI_ButtonPress {
|
||||
Pressed
|
||||
} else {
|
||||
@@ -632,6 +711,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
device_id,
|
||||
state,
|
||||
button: Left,
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
ffi::Button2 => callback(Event::WindowEvent {
|
||||
@@ -640,6 +720,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
device_id,
|
||||
state,
|
||||
button: Middle,
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
ffi::Button3 => callback(Event::WindowEvent {
|
||||
@@ -648,6 +729,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
device_id,
|
||||
state,
|
||||
button: Right,
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -668,34 +750,19 @@ impl<T: 'static> EventProcessor<T> {
|
||||
_ => unreachable!(),
|
||||
},
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
8 => callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Back,
|
||||
},
|
||||
}),
|
||||
9 => callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Forward,
|
||||
},
|
||||
}),
|
||||
|
||||
x => callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Other(x as u16),
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
}
|
||||
@@ -706,6 +773,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
let window_id = mkwid(xev.event);
|
||||
let new_cursor_pos = (xev.event_x, xev.event_y);
|
||||
|
||||
let modifiers = ModifiersState::from_x11(&xev.mods);
|
||||
update_modifiers!(modifiers, None);
|
||||
|
||||
let cursor_moved = self.with_window(xev.event, |window| {
|
||||
let mut shared_state_lock = window.shared_state_lock();
|
||||
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
|
||||
@@ -718,6 +788,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
} else if cursor_moved.is_none() {
|
||||
@@ -764,6 +835,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
},
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -817,11 +889,25 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
// The mods field on this event isn't actually populated, so query the
|
||||
// pointer device. In the future, we can likely remove this round-trip by
|
||||
// relying on `Xkb` for modifier values.
|
||||
//
|
||||
// This needs to only be done after confirming the window still exists,
|
||||
// since otherwise we risk getting a `BadWindow` error if the window was
|
||||
// dropped with queued events.
|
||||
let modifiers = wt
|
||||
.xconn
|
||||
.query_pointer(xev.event, xev.deviceid)
|
||||
.expect("Failed to query pointer device")
|
||||
.get_modifier_state();
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: CursorMoved {
|
||||
device_id,
|
||||
position,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -849,10 +935,14 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.focus(xev.event)
|
||||
.expect("Failed to focus input context");
|
||||
|
||||
let modifiers = ModifiersState::from_x11(&xev.mods);
|
||||
|
||||
self.device_mod_state.update_state(&modifiers, None);
|
||||
|
||||
if self.active_window != Some(xev.event) {
|
||||
self.active_window = Some(xev.event);
|
||||
|
||||
wt.update_listen_device_events(true);
|
||||
wt.update_device_event_filter(true);
|
||||
|
||||
let window_id = mkwid(xev.event);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
@@ -866,12 +956,10 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: Focused(true),
|
||||
});
|
||||
|
||||
let modifiers: crate::keyboard::ModifiersState =
|
||||
self.kb_state.mods_state().into();
|
||||
if !modifiers.is_empty() {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(modifiers.into()),
|
||||
event: WindowEvent::ModifiersChanged(modifiers),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -889,6 +977,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: CursorMoved {
|
||||
device_id: mkdid(pointer_id),
|
||||
position,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -897,7 +986,8 @@ impl<T: 'static> EventProcessor<T> {
|
||||
wt,
|
||||
window_id,
|
||||
ElementState::Pressed,
|
||||
&mut self.kb_state,
|
||||
&self.mod_keymap,
|
||||
&mut self.device_mod_state,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
@@ -916,22 +1006,21 @@ impl<T: 'static> EventProcessor<T> {
|
||||
if self.active_window.take() == Some(xev.event) {
|
||||
let window_id = mkwid(xev.event);
|
||||
|
||||
wt.update_listen_device_events(false);
|
||||
wt.update_device_event_filter(false);
|
||||
|
||||
// Issue key release events for all pressed keys
|
||||
Self::handle_pressed_keys(
|
||||
wt,
|
||||
window_id,
|
||||
ElementState::Released,
|
||||
&mut self.kb_state,
|
||||
&self.mod_keymap,
|
||||
&mut self.device_mod_state,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(
|
||||
ModifiersState::empty().into(),
|
||||
),
|
||||
event: WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
||||
});
|
||||
|
||||
if let Some(window) = self.with_window(xev.event, Arc::clone) {
|
||||
@@ -956,6 +1045,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
};
|
||||
if self.window_exists(xev.event) {
|
||||
let id = xev.detail as u64;
|
||||
let modifiers = self.device_mod_state.modifiers();
|
||||
let location = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
// Mouse cursor position changes when touch events are received.
|
||||
@@ -967,6 +1057,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
|
||||
position: location.cast(),
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1051,6 +1142,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => {
|
||||
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
@@ -1061,19 +1153,46 @@ impl<T: 'static> EventProcessor<T> {
|
||||
};
|
||||
|
||||
let device_id = mkdid(xev.sourceid);
|
||||
let keycode = xev.detail as u32;
|
||||
if keycode < KEYCODE_OFFSET as u32 {
|
||||
let keycode = xev.detail;
|
||||
let scancode = keycode - KEYCODE_OFFSET as i32;
|
||||
if scancode < 0 {
|
||||
return;
|
||||
}
|
||||
let physical_key = keymap::raw_keycode_to_keycode(keycode);
|
||||
let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode);
|
||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
||||
let modifiers = self.device_mod_state.modifiers();
|
||||
|
||||
#[allow(deprecated)]
|
||||
callback(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::Key(RawKeyEvent {
|
||||
physical_key,
|
||||
event: DeviceEvent::Key(KeyboardInput {
|
||||
scancode: scancode as u32,
|
||||
virtual_keycode,
|
||||
state,
|
||||
modifiers,
|
||||
}),
|
||||
});
|
||||
|
||||
if let Some(modifier) =
|
||||
self.mod_keymap.get_modifier(keycode as ffi::KeyCode)
|
||||
{
|
||||
self.device_mod_state.key_event(
|
||||
state,
|
||||
keycode as ffi::KeyCode,
|
||||
modifier,
|
||||
);
|
||||
|
||||
let new_modifiers = self.device_mod_state.modifiers();
|
||||
|
||||
if modifiers != new_modifiers {
|
||||
if let Some(window_id) = self.active_window {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window_id),
|
||||
event: WindowEvent::ModifiersChanged(new_modifiers),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ffi::XI_HierarchyChanged => {
|
||||
@@ -1103,55 +1222,6 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if event_type == self.xkbext.first_event_id {
|
||||
let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) };
|
||||
match xev.xkb_type {
|
||||
ffi::XkbNewKeyboardNotify => {
|
||||
let xev = unsafe {
|
||||
&*(xev as *const _ as *const ffi::XkbNewKeyboardNotifyEvent)
|
||||
};
|
||||
let keycodes_changed_flag = 0x1;
|
||||
let geometry_changed_flag = 0x1 << 1;
|
||||
|
||||
let keycodes_changed =
|
||||
util::has_flag(xev.changed, keycodes_changed_flag);
|
||||
let geometry_changed =
|
||||
util::has_flag(xev.changed, geometry_changed_flag);
|
||||
|
||||
if xev.device == self.kb_state.core_keyboard_id
|
||||
&& (keycodes_changed || geometry_changed)
|
||||
{
|
||||
unsafe { self.kb_state.init_with_x11_keymap() };
|
||||
}
|
||||
}
|
||||
ffi::XkbStateNotify => {
|
||||
let xev =
|
||||
unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) };
|
||||
|
||||
let prev_mods = self.kb_state.mods_state();
|
||||
self.kb_state.update_modifiers(
|
||||
xev.base_mods,
|
||||
xev.latched_mods,
|
||||
xev.locked_mods,
|
||||
xev.base_group as u32,
|
||||
xev.latched_group as u32,
|
||||
xev.locked_group as u32,
|
||||
);
|
||||
let new_mods = self.kb_state.mods_state();
|
||||
if prev_mods != new_mods {
|
||||
if let Some(window) = self.active_window {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::ModifiersChanged(
|
||||
Into::<ModifiersState>::into(new_mods).into(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if event_type == self.randr_event_offset {
|
||||
// In the future, it would be quite easy to emit monitor hotplug events.
|
||||
let prev_list = monitor::invalidate_cached_monitor_list();
|
||||
@@ -1275,12 +1345,14 @@ impl<T: 'static> EventProcessor<T> {
|
||||
wt: &super::EventLoopWindowTarget<T>,
|
||||
window_id: crate::window::WindowId,
|
||||
state: ElementState,
|
||||
kb_state: &mut KbdState,
|
||||
mod_keymap: &ModifierKeymap,
|
||||
device_mod_state: &mut ModifierKeyState,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(Event<'_, T>),
|
||||
{
|
||||
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
|
||||
let modifiers = device_mod_state.modifiers();
|
||||
|
||||
// Update modifiers state and emit key events based on which keys are currently pressed.
|
||||
for keycode in wt
|
||||
@@ -1289,13 +1361,29 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.into_iter()
|
||||
.filter(|k| *k >= KEYCODE_OFFSET)
|
||||
{
|
||||
let keycode = keycode as u32;
|
||||
let event = kb_state.process_key_event(keycode, state, false);
|
||||
let scancode = (keycode - KEYCODE_OFFSET) as u32;
|
||||
let keysym = wt.xconn.keycode_to_keysym(keycode);
|
||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
||||
|
||||
if let Some(modifier) = mod_keymap.get_modifier(keycode as ffi::KeyCode) {
|
||||
device_mod_state.key_event(
|
||||
ElementState::Pressed,
|
||||
keycode as ffi::KeyCode,
|
||||
modifier,
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
input: KeyboardInput {
|
||||
scancode,
|
||||
state,
|
||||
virtual_keycode,
|
||||
modifiers,
|
||||
},
|
||||
is_synthetic: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -263,7 +263,7 @@ impl ImeContext {
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
unsafe fn create_preedit_ic(
|
||||
@@ -302,7 +302,7 @@ impl ImeContext {
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
unsafe fn create_nothing_ic(
|
||||
@@ -320,7 +320,7 @@ impl ImeContext {
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
(!ic.is_null()).then(|| ic)
|
||||
}
|
||||
|
||||
pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
mod dnd;
|
||||
mod event_processor;
|
||||
mod events;
|
||||
pub mod ffi;
|
||||
mod ime;
|
||||
mod monitor;
|
||||
@@ -17,38 +18,38 @@ pub(crate) use self::{
|
||||
|
||||
pub use self::xdisplay::{XError, XNotSupported};
|
||||
|
||||
use calloop::channel::{channel, Channel, Event as ChanResult, Sender};
|
||||
use calloop::generic::Generic;
|
||||
use calloop::{Dispatcher, EventLoop as Loop};
|
||||
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::CStr,
|
||||
mem::{self, MaybeUninit},
|
||||
ops::Deref,
|
||||
os::raw::*,
|
||||
os::unix::io::RawFd,
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice,
|
||||
sync::mpsc::{Receiver, Sender, TryRecvError},
|
||||
sync::{mpsc, Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use libc::{self, setlocale, LC_CTYPE};
|
||||
|
||||
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
|
||||
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
|
||||
|
||||
use self::{
|
||||
dnd::{Dnd, DndState},
|
||||
event_processor::EventProcessor,
|
||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
|
||||
util::modifiers::ModifierKeymap,
|
||||
};
|
||||
use super::common::xkb_state::KbdState;
|
||||
use crate::{
|
||||
error::OsError as RootOsError,
|
||||
event::{Event, StartCause},
|
||||
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
event_loop::{
|
||||
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
||||
},
|
||||
platform_impl::{
|
||||
platform::{sticky_exit_callback, WindowId},
|
||||
PlatformSpecificWindowBuilderAttributes,
|
||||
@@ -56,7 +57,47 @@ use crate::{
|
||||
window::WindowAttributes,
|
||||
};
|
||||
|
||||
type X11Source = Generic<RawFd>;
|
||||
const X_TOKEN: Token = Token(0);
|
||||
const USER_REDRAW_TOKEN: Token = Token(1);
|
||||
|
||||
struct WakeSender<T> {
|
||||
sender: Sender<T>,
|
||||
waker: Arc<Waker>,
|
||||
}
|
||||
|
||||
struct PeekableReceiver<T> {
|
||||
recv: Receiver<T>,
|
||||
first: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> PeekableReceiver<T> {
|
||||
pub fn from_recv(recv: Receiver<T>) -> Self {
|
||||
Self { recv, first: None }
|
||||
}
|
||||
pub fn has_incoming(&mut self) -> bool {
|
||||
if self.first.is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self.recv.try_recv() {
|
||||
Ok(v) => {
|
||||
self.first = Some(v);
|
||||
true
|
||||
}
|
||||
Err(TryRecvError::Empty) => false,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
warn!("Channel was disconnected when checking incoming");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
|
||||
if let Some(first) = self.first.take() {
|
||||
return Ok(first);
|
||||
}
|
||||
self.recv.try_recv()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
xconn: Arc<XConnection>,
|
||||
@@ -66,40 +107,31 @@ pub struct EventLoopWindowTarget<T> {
|
||||
root: ffi::Window,
|
||||
ime: RefCell<Ime>,
|
||||
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
||||
redraw_sender: Sender<WindowId>,
|
||||
device_events: Cell<DeviceEvents>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
device_event_filter: Cell<DeviceEventFilter>,
|
||||
_marker: ::std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
event_loop: Loop<'static, EventLoopState<T>>,
|
||||
poll: Poll,
|
||||
waker: Arc<Waker>,
|
||||
event_processor: EventProcessor<T>,
|
||||
redraw_receiver: PeekableReceiver<WindowId>,
|
||||
user_receiver: PeekableReceiver<T>, //waker.wake needs to be called whenever something gets sent
|
||||
user_sender: Sender<T>,
|
||||
target: Rc<RootELW<T>>,
|
||||
|
||||
/// The current state of the event loop.
|
||||
state: EventLoopState<T>,
|
||||
|
||||
/// Dispatcher for redraw events.
|
||||
redraw_dispatcher: Dispatcher<'static, Channel<WindowId>, EventLoopState<T>>,
|
||||
}
|
||||
|
||||
struct EventLoopState<T> {
|
||||
/// Incoming user events.
|
||||
user_events: VecDeque<T>,
|
||||
|
||||
/// Incoming redraw events.
|
||||
redraw_events: VecDeque<WindowId>,
|
||||
}
|
||||
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
user_sender: Sender<T>,
|
||||
waker: Arc<Waker>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EventLoopProxy {
|
||||
user_sender: self.user_sender.clone(),
|
||||
waker: self.waker.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,36 +202,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
ext
|
||||
};
|
||||
|
||||
let xkbext = {
|
||||
let mut ext = XExtension::default();
|
||||
|
||||
let res = unsafe {
|
||||
(xconn.xlib.XkbQueryExtension)(
|
||||
xconn.display,
|
||||
&mut ext.opcode,
|
||||
&mut ext.first_event_id,
|
||||
&mut ext.first_error_id,
|
||||
&mut 1,
|
||||
&mut 0,
|
||||
)
|
||||
};
|
||||
|
||||
if res == ffi::False {
|
||||
panic!("X server missing XKB extension");
|
||||
}
|
||||
|
||||
// Enable detectable auto repeat.
|
||||
let mut supported = 0;
|
||||
unsafe {
|
||||
(xconn.xlib.XkbSetDetectableAutoRepeat)(xconn.display, 1, &mut supported);
|
||||
}
|
||||
if supported == 0 {
|
||||
warn!("Detectable auto repeart is not supported");
|
||||
}
|
||||
|
||||
ext
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut xinput_major_ver = ffi::XI_2_Major;
|
||||
let mut xinput_minor_ver = ffi::XI_2_Minor;
|
||||
@@ -217,45 +219,18 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
xconn.update_cached_wm_info(root);
|
||||
|
||||
// Create an event loop.
|
||||
let event_loop =
|
||||
Loop::<EventLoopState<T>>::try_new().expect("Failed to initialize the event loop");
|
||||
let handle = event_loop.handle();
|
||||
let mut mod_keymap = ModifierKeymap::new();
|
||||
mod_keymap.reset_from_x_connection(&xconn);
|
||||
|
||||
// Create the X11 event dispatcher.
|
||||
let source = X11Source::new(xconn.x11_fd, calloop::Interest::READ, calloop::Mode::Level);
|
||||
handle
|
||||
.insert_source(source, |_, _, _| Ok(calloop::PostAction::Continue))
|
||||
.expect("Failed to register the X11 event dispatcher");
|
||||
let poll = Poll::new().unwrap();
|
||||
let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap());
|
||||
|
||||
// Create a channel for sending user events.
|
||||
let (user_sender, user_channel) = channel();
|
||||
handle
|
||||
.insert_source(user_channel, |ev, _, state| {
|
||||
if let ChanResult::Msg(user) = ev {
|
||||
state.user_events.push_back(user);
|
||||
}
|
||||
})
|
||||
.expect("Failed to register the user event channel with the event loop");
|
||||
poll.registry()
|
||||
.register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE)
|
||||
.unwrap();
|
||||
|
||||
// Create a channel for handling redraw requests.
|
||||
let (redraw_sender, redraw_channel) = channel();
|
||||
|
||||
// Create a dispatcher for the redraw channel such that we can dispatch it independent of the
|
||||
// event loop.
|
||||
let redraw_dispatcher =
|
||||
Dispatcher::<_, EventLoopState<T>>::new(redraw_channel, |ev, _, state| {
|
||||
if let ChanResult::Msg(window_id) = ev {
|
||||
state.redraw_events.push_back(window_id);
|
||||
}
|
||||
});
|
||||
handle
|
||||
.register_dispatcher(redraw_dispatcher.clone())
|
||||
.expect("Failed to register the redraw event channel with the event loop");
|
||||
|
||||
let kb_state =
|
||||
KbdState::from_x11_xkb(unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) })
|
||||
.unwrap();
|
||||
let (user_sender, user_channel) = std::sync::mpsc::channel();
|
||||
let (redraw_sender, redraw_channel) = std::sync::mpsc::channel();
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
ime,
|
||||
@@ -266,12 +241,15 @@ impl<T: 'static> EventLoop<T> {
|
||||
xconn,
|
||||
wm_delete_window,
|
||||
net_wm_ping,
|
||||
redraw_sender,
|
||||
device_events: Default::default(),
|
||||
redraw_sender: WakeSender {
|
||||
sender: redraw_sender, // not used again so no clone
|
||||
waker: waker.clone(),
|
||||
},
|
||||
device_event_filter: Default::default(),
|
||||
};
|
||||
|
||||
// Set initial device event filter.
|
||||
window_target.update_listen_device_events(true);
|
||||
window_target.update_device_event_filter(true);
|
||||
|
||||
let target = Rc::new(RootELW {
|
||||
p: super::EventLoopWindowTarget::X(window_target),
|
||||
@@ -286,13 +264,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
ime_receiver,
|
||||
ime_event_receiver,
|
||||
xi2ext,
|
||||
xkbext,
|
||||
kb_state,
|
||||
mod_keymap,
|
||||
device_mod_state: Default::default(),
|
||||
num_touch: 0,
|
||||
first_touch: None,
|
||||
active_window: None,
|
||||
is_composing: false,
|
||||
got_key_release: true,
|
||||
};
|
||||
|
||||
// Register for device hotplug events
|
||||
@@ -302,33 +279,23 @@ impl<T: 'static> EventLoop<T> {
|
||||
.select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask)
|
||||
.queue();
|
||||
|
||||
get_xtarget(&target)
|
||||
.xconn
|
||||
.select_xkb_events(
|
||||
0x100, // Use the "core keyboard device"
|
||||
ffi::XkbNewKeyboardNotifyMask | ffi::XkbStateNotifyMask,
|
||||
)
|
||||
.unwrap()
|
||||
.queue();
|
||||
|
||||
event_processor.init_device(ffi::XIAllDevices);
|
||||
|
||||
EventLoop {
|
||||
event_loop,
|
||||
poll,
|
||||
waker,
|
||||
event_processor,
|
||||
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
|
||||
user_receiver: PeekableReceiver::from_recv(user_channel),
|
||||
user_sender,
|
||||
target,
|
||||
redraw_dispatcher,
|
||||
state: EventLoopState {
|
||||
user_events: VecDeque::new(),
|
||||
redraw_events: VecDeque::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy {
|
||||
user_sender: self.user_sender.clone(),
|
||||
waker: self.waker.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +344,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
// Empty the user event buffer
|
||||
{
|
||||
while let Some(event) = this.state.user_events.pop_front() {
|
||||
while let Ok(event) = this.user_receiver.try_recv() {
|
||||
sticky_exit_callback(
|
||||
crate::event::Event::UserEvent(event),
|
||||
&this.target,
|
||||
@@ -395,19 +362,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
// Quickly dispatch all redraw events to avoid buffering them.
|
||||
while let Ok(event) = this.redraw_dispatcher.as_source_mut().try_recv() {
|
||||
this.state.redraw_events.push_back(event);
|
||||
}
|
||||
|
||||
// Empty the redraw requests
|
||||
{
|
||||
let mut windows = HashSet::new();
|
||||
|
||||
// Empty the channel.
|
||||
|
||||
while let Some(window_id) = this.state.redraw_events.pop_front() {
|
||||
while let Ok(window_id) = this.redraw_receiver.try_recv() {
|
||||
windows.insert(window_id);
|
||||
}
|
||||
|
||||
@@ -477,6 +436,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
let mut control_flow = ControlFlow::default();
|
||||
let mut events = Events::with_capacity(8);
|
||||
let mut cause = StartCause::Init;
|
||||
|
||||
// run the initial loop iteration
|
||||
@@ -487,23 +447,22 @@ impl<T: 'static> EventLoop<T> {
|
||||
break code;
|
||||
}
|
||||
let has_pending = self.event_processor.poll()
|
||||
|| !self.state.user_events.is_empty()
|
||||
|| !self.state.redraw_events.is_empty();
|
||||
|| self.user_receiver.has_incoming()
|
||||
|| self.redraw_receiver.has_incoming();
|
||||
if !has_pending {
|
||||
// Wait until
|
||||
if let Err(error) = self
|
||||
.event_loop
|
||||
.dispatch(iter_result.timeout, &mut self.state)
|
||||
.map_err(std::io::Error::from)
|
||||
{
|
||||
break error.raw_os_error().unwrap_or(1);
|
||||
if let Err(e) = self.poll.poll(&mut events, iter_result.timeout) {
|
||||
if e.raw_os_error() != Some(libc::EINTR) {
|
||||
panic!("epoll returned an error: {e:?}");
|
||||
}
|
||||
}
|
||||
events.clear();
|
||||
|
||||
if control_flow == ControlFlow::Wait {
|
||||
// We don't go straight into executing the event loop iteration, we instead go
|
||||
// to the start of this loop and check again if there's any pending event. We
|
||||
// must do this because during the execution of the iteration we sometimes wake
|
||||
// the calloop waker, and if the waker is already awaken before we call poll(),
|
||||
// the mio waker, and if the waker is already awaken before we call poll(),
|
||||
// then poll doesn't block, but it returns immediately. This caused the event
|
||||
// loop to run continuously even if the control_flow was `Wait`
|
||||
continue;
|
||||
@@ -557,7 +516,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
control_flow,
|
||||
&mut |event, window_target, control_flow| {
|
||||
if let Event::RedrawRequested(crate::window::WindowId(wid)) = event {
|
||||
wt.redraw_sender.send(wid).unwrap();
|
||||
wt.redraw_sender.sender.send(wid).unwrap();
|
||||
wt.redraw_sender.waker.wake().unwrap();
|
||||
} else {
|
||||
callback(event, window_target, control_flow);
|
||||
}
|
||||
@@ -583,17 +543,17 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
&self.xconn
|
||||
}
|
||||
|
||||
pub fn set_listen_device_events(&self, allowed: DeviceEvents) {
|
||||
self.device_events.set(allowed);
|
||||
pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {
|
||||
self.device_event_filter.set(filter);
|
||||
}
|
||||
|
||||
/// Update the device event based on window focus.
|
||||
pub fn update_listen_device_events(&self, focus: bool) {
|
||||
let device_events = self.device_events.get() == DeviceEvents::Always
|
||||
|| (focus && self.device_events.get() == DeviceEvents::WhenFocused);
|
||||
/// Update the device event filter based on window focus.
|
||||
pub fn update_device_event_filter(&self, focus: bool) {
|
||||
let filter_events = self.device_event_filter.get() == DeviceEventFilter::Never
|
||||
|| (self.device_event_filter.get() == DeviceEventFilter::Unfocused && !focus);
|
||||
|
||||
let mut mask = 0;
|
||||
if device_events {
|
||||
if !filter_events {
|
||||
mask = ffi::XI_RawMotionMask
|
||||
| ffi::XI_RawButtonPressMask
|
||||
| ffi::XI_RawButtonReleaseMask
|
||||
@@ -620,6 +580,7 @@ impl<T: 'static> EventLoopProxy<T> {
|
||||
self.user_sender
|
||||
.send(event)
|
||||
.map_err(|e| EventLoopClosed(e.0))
|
||||
.map(|_| self.waker.wake().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user