Compare commits

..

13 Commits

Author SHA1 Message Date
John Nunley
d3657449ab Review comments
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
9382b7238c Eyepatch for the android linker error
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
73348f49d7 fmt
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
49a315870e Review comments
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
999a1f8733 Hacky way to exclude 1.65.0 on Android
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
023ab591b5 Disable test builds on Android
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
af32c5c298 Don't use CMD subselection on Android
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
0c4e725692 Fix compile error in xbuild-target
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
8671481db4 Once more unto the breach!
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
fe893e6690 Empty commit to re-run CI
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
5cfdb3fc42 Typo!
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
e30f112516 Fix malformed install-action call
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
f9a975e6e5 Use xbuild instead of cargo apk in the CI
In addition to cargo apk being deprecated, xbuild also has support for
ios.

Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
135 changed files with 5177 additions and 6615 deletions

View File

@@ -35,19 +35,20 @@ jobs:
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Android is tested on stable-3
- toolchain: '1.65.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
# Android is tested on stable-3
- toolchain: '1.69.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' }
- toolchain: 'stable'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' }
- toolchain: 'nightly'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' }
env:
# Set more verbose terminal output
@@ -59,7 +60,6 @@ jobs:
RUSTDOCFLAGS: '--deny=warnings'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
CMD: ${{ matrix.platform.cmd }}
steps:
- uses: actions/checkout@v3
@@ -87,23 +87,11 @@ jobs:
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Cache cargo-apk
if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache
uses: actions/cache@v3
- name: Install xbuild
uses: taiki-e/install-action@v2
if: contains(matrix.platform.target, 'android') || contains(matrix.platform.target, 'ios')
with:
path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version
key: cargo-apk-v0-9-7
- uses: dtolnay/rust-toolchain@master
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
with:
toolchain: stable
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked
tool: xbuild@0.2.0
- uses: dtolnay/rust-toolchain@master
with:
@@ -111,17 +99,30 @@ jobs:
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install LLVM tools for Android
if: contains(matrix.platform.target, 'android')
run: sudo apt-get update && sudo apt-get install lld llvm
- name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items
- name: Build crate
run: cargo $CMD build $OPTIONS
- name: Build crate
run: cargo build $OPTIONS
- name: Build and package crate for Android
if: contains(matrix.platform.target, 'android')
run: x build -p android-xbuild-target --platform android --arch arm64
- name: Build and package crate for iOS
if: contains(matrix.platform.target, 'ios')
run: x build -p ios-xbuild-target --platform ios --arch arm64
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
!contains(matrix.platform.target, 'android') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test --no-run $OPTIONS
run: cargo test --no-run $OPTIONS
- name: Run tests
if: >
@@ -130,7 +131,7 @@ jobs:
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test $OPTIONS
run: cargo test $OPTIONS
- name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
@@ -139,8 +140,9 @@ jobs:
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
!contains(matrix.platform.target, 'android') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test --no-run $OPTIONS --features serde
run: cargo test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled
if: >
@@ -149,7 +151,7 @@ jobs:
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test $OPTIONS --features serde
run: cargo test $OPTIONS --features serde
# See restore step above
- name: Save cache of cargo folder

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "deps/apk-builder"]
path = deps/apk-builder
url = https://github.com/rust-windowing/android-rs-glue

View File

@@ -2,121 +2,49 @@
All notable changes to this project will be documented in this file.
Please keep one empty line before and after all headers. (This is required for
`git` to produce a conflict when a release is made while a PR is open and the
PR's changelog entry would go into the wrong section).
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
And please only add new entries to the top of this list, right below the `#
Unreleased` header.
And please only add new entries to the top of this list, right below the `# Unreleased` header.
# Unreleased
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- Add `Window::set_custom_cursor`
- Add `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- On macOS, add services menu.
- On macOS, remove spurious error logging when handling `Fn`.
- On X11, fix an issue where floating point data from the server is
misinterpreted during a drag and drop operation.
- On X11, fix a bug where focusing the window would panic.
- On macOS, fix `refresh_rate_millihertz`.
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
- On X11, fix `Xft.dpi` detection from Xresources.
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
- On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility.
# 0.29.4
- Fix crash when running iOS app on macOS.
- On X11, check common alternative cursor names when loading cursor.
- On X11, reload the DPI after a property change event.
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
- On Wayland, fix `wl_surface` being destroyed before associated objects.
- On macOS, fix assertion when pressing `Fn` key.
# 0.29.3
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
- On Wayland, ignore resize requests when the window is fully tiled.
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
- On Windows, add support for `Window::set_transparent`.
- On macOS, fix deadlock when entering a nested event loop from an event handler.
- On macOS, add support for `Window::set_blur`.
# 0.29.2
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
- **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android.
- **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`.
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
- Renamed `EventLoopExtRunOnDemand` / `run_ondemand` to `EventLoopExtRunOnDemand` / `run_on_demand`.
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
- On Web, never return a `MonitorHandle`.
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
- On macOS, fix crash in `window.set_minimized(false)`.
- On Web, enable event propagation and let `DeviceEvent`s appear after `WindowEvent`s.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
- Mark `startup_notify` unsafe functions as safe.
- Fix a bug where Wayland would be chosen on Linux even if the user specified `with_x11`. (#3058)
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back.
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
- Implement `PartialOrd` and `Ord` for `MouseButton`.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
- **Breaking:** remove `DeviceEvent::Text`.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- **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`.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- **Breaking:** `CursorIcon::Arrow` was removed.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- **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 `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`.
- 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 `NamedKey` to represent the categorized keys.
- 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 `PhysicalKeyExtScancode`, which lets you convert between scancodes and
`PhysicalKey`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- On Android, fix `DeviceId` to contain device id's.
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- **Breaking** add `Event::MemoryWarning`; implemented on iOS/Android.
- On Wayland, support `Occluded` event with xdg-shell v6
# 0.29.1-beta
- **Breaking:** Bump `ndk` version to `0.8.0-beta.0`, ndk-sys to `v0.5.0-beta.0`, `android-activity` to `0.5.0-beta.1`.
- **Breaking:** Bump MSRV from `1.64` to `1.65`.
- Make iOS windows usable from other threads.
- Reexport `raw-window-handle` in `window` module.
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
- On X11, set `visual_id` in returned `raw-window-handle`.
- **Breaking:** on Wayland, dispatching user created wayland queue won't wake up the loop unless winit has event to send back.
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
- `platform::windows::HINSTANCE`.
- `WindowExtWindows::hinstance`.
@@ -133,89 +61,126 @@ Unreleased` header.
- `WindowExtX11::xlib_display`.
- `WindowExtX11::xlib_screen_id`.
- `WindowExtX11::xcb_connection`.
- Reexport `raw-window-handle` in `window` module.
- Add `ElementState::is_pressed`.
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- Implement `AsFd`/`AsRawFd` for `EventLoop<T>` on X11 and Wayland.
- Implement `PartialOrd` and `Ord` for `MouseButton`.
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
- Make `WindowBuilder` `Send + Sync`.
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- Make iOS windows usable from other threads.
- On Android, add force data to touch events.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- On Android, fix `DeviceId` to contain device id's.
- On Orbital, fix `ModifiersChanged` not being sent.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable.
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
- On Wayland, fix forward compatibility issues.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On Wayland, support `Occluded` event with xdg-shell v6
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
- On Web, `EventLoopProxy` now implements `Send`.
- On Web, `Window` now implements `Send` and `Sync`.
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- Implement `PartialOrd` and `Ord` for `Key`, `KeyCode`, `NativeKey`, and `NativeKeyCode`.
- Add `ElementState::is_pressed`.
- On Web, implement `WindowEvent::Occluded`.
- On Web, fix touch location to be as accurate as mouse position.
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
- On Web, add Fullscreen API compatibility for Safari.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support.
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Web, allow event loops to be recreated with `spawn`.
- On Web, enable event propagation.
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation.
- On Web, fix pen treated as mouse input.
- On Web, fix pointer button events not being processed when a buttons is already pressed.
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix touch input not gaining or loosing focus.
- On Web, fix touch location to be as accurate as mouse position.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- On Web, implement `Window::focus_window()`.
- On Web, implement `Window::set_(min|max)_inner_size()`.
- On Web, implement `WindowEvent::Occluded`.
- On Web, never return a `MonitorHandle`.
- On Web, prevent clicks on the canvas to select text.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, implement `Window::focus_window()`.
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, send mouse position on button release as well.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
- 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, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Windows, add `drag_resize_window` method support.
- **Breaking** `run() ->!` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_ondemand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- Added `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On iOS, add force data to touch events when using the Apple Pencil.
- On Android, add force data to touch events.
# 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 Windows, add `drag_resize_window` method support.
- On Windows, add horizontal MouseWheel `DeviceEvent`.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- On Windows, fix IME APIs not working when from non event loop thread.
- On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse.
- On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`.
- 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 X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- On X11, set `visual_id` in returned `raw-window-handle`.
- On iOS, add ability to change the status bar style.
- On iOS, add force data to touch events when using the Apple Pencil.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- On macOS, fix assertion when pressing `Globe` key.
- On macOS, fix crash in `window.set_minimized(false)`.
- 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.7

View File

@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
When reporting an issue, in order to help the maintainers understand what the problem is, please make
your description of the issue as detailed as possible:
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit
@@ -21,7 +21,7 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
@@ -34,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
@@ -46,26 +46,27 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
## Release process
Given that winit is a widely used library, we should be able to make a patch
Given that winit is a widely used library we should be able to make a patch
releases at any time we want without blocking the development of new features.
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
To achieve these goals, a new branch is created for every new release. Releases
and later patch releases are committed and tagged in this branch.
The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially, the version on the latest master is `0.1.0`
1. Initially the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release
3. In the branch, the version is bumped to `v0.2.0`
4. The new commit in the branch is tagged `v0.2.0`
5. The version is pushed to crates.io
6. A GitHub release is created for the `v0.2.0` tag
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
When doing a patch release, the process is similar:
1. Initially, the version of the latest release is `0.2.0`
When doing a patch release the process is similar:
1. Initially the version of the latest release is `0.2.0`
2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 3-7 of the regular release example

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.29.4"
version = "0.29.1-beta"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -13,14 +13,7 @@ categories = ["gui"]
rust-version = "1.65.0"
[package.metadata.docs.rs]
features = [
"rwh_04",
"rwh_05",
"rwh_06",
"serde",
# Enabled to get docs to compile
"android-native-activity",
]
features = ["rwh_04", "rwh_05", "rwh_06", "serde"]
default-target = "x86_64-unknown-linux-gnu"
# These are all tested in CI
targets = [
@@ -43,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", "xim"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
@@ -52,16 +45,13 @@ 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"]
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies]
cfg_aliases = "0.1.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.1.0"
cursor-icon = "1.0.0"
log = "0.4"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
@@ -73,16 +63,16 @@ smol_str = "0.2.0"
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "4.2.0", default_features = false }
winit = { path = ".", features = ["rwh_05"] }
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]
android-activity = "0.5.0"
ndk = { version = "0.8.0", default-features = false }
ndk-sys = "0.5.0"
# Coordinate the next winit release android-activity 0.5 release
android-activity = "=0.5.0-beta.1"
ndk = "=0.8.0-beta.0"
ndk-sys = "=0.5.0-beta.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
@@ -160,14 +150,13 @@ memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xim = { version = "0.3.0", features = ["x11rb-client", "client"], optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0"
[target.'cfg(target_os = "redox")'.dependencies]
@@ -180,7 +169,6 @@ version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'console',
'CssStyleDeclaration',
'Document',
@@ -192,10 +180,6 @@ features = [
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
@@ -205,7 +189,6 @@ features = [
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
@@ -213,8 +196,7 @@ features = [
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Url',
'WheelEvent'
]
[target.'cfg(target_family = "wasm")'.dependencies]
@@ -230,8 +212,7 @@ web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[workspace]
members = [
"xbuild/android-target",
"xbuild/ios-target",
"run-wasm",
]
[patch.crates-io]
xim = { git = "https://github.com/forkgull/xim-rs", branch = "x11rb-13" }

View File

@@ -1,6 +1,6 @@
# Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling and can
Winit aims to expose an interface that abstracts over window creation and input handling, and can
be used to create both games and applications. It supports the following main graphical platforms:
- Desktop
- Windows 7+ (10+ is tested regularly)
@@ -45,10 +45,10 @@ be released and the library will enter maintenance mode. For the most part, new
be added past this point. New platform features may be accepted and exposed through point releases.
### Tier upgrades
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
Some platform features could in theory be exposed across multiple platforms, but have not gone
through the implementation work necessary to function on all platforms. When one of these features
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently
If that gets accepted, the platform-specific functions gets deprecated and become permanently
exposed through the core, cross-platform API.
# Features
@@ -88,7 +88,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive
for fullscreen windows, and if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
@@ -105,8 +105,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
@@ -151,13 +150,13 @@ If your PR makes notable changes to Winit's features, please update this section
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Status bar visibility
* Deferrring system gestures
* Getting the device idiom
* Getting the preferred video mode
### Web
* Get if the systems preferred color scheme is "dark"
* Get if systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
@@ -167,7 +166,7 @@ If your PR makes notable changes to Winit's features, please update this section
Legend:
- ✔️: Works as intended
- ▢: Mostly works, but some bugs are known
- ▢: Mostly works but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
@@ -207,7 +206,6 @@ Legend:
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |

View File

@@ -2,20 +2,21 @@
The winit maintainers would like to recognize the following former winit
contributors, without whom winit would not exist in its current form. We thank
them deeply for their time and efforts and wish them the best of luck in their
them deeply for their time and efforts, and wish them best of luck in their
future endeavors:
* [@tomaka]: For creating the winit project and guiding it through its early
years of existence.
* [@vberger]: For diligently creating the Wayland backend and being its
* [@vberger]: For diligently creating the Wayland backend, and being its
extremely helpful and benevolent maintainer for years.
* [@francesca64]: For taking over the responsibility of maintaining almost every
winit backend and standardizing HiDPI support across all of them.
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a
winit backend, and standardizing HiDPI support across all of them.
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
vastly more sustainable era of winit.
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
stellar dedication to improving both winit and glutin.
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API.
* [@ArturKovacs]: For consistently maintaining the macOS backend, and his
immense involvement in designing and implementing the new keyboard API.
[@tomaka]: https://github.com/tomaka
[@vberger]: https://github.com/vberger

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.29.4"
winit = "0.29.1-beta"
```
## [Documentation](https://docs.rs/winit)
@@ -26,12 +26,38 @@ Join us in any of these:
Winit is a window creation and management library. It can create windows and lets you handle
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
produced by the window.
produced by window.
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
show something on the window you need to use the platform-specific getters provided by winit, or
another library.
```rust
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
event_loop.set_control_flow(ControlFlow::Wait);
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, elwt| {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
_ => (),
}
});
}
```
Winit is only officially supported on the latest stable version of the Rust compiler.
### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
@@ -42,7 +68,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following
@@ -53,11 +79,12 @@ min(sid, stable - 3)
```
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
`stable` is the latest stable version of Rust. This bound may be broken in the
event of a major ecosystem shift or a security vulnerability.
[Debian Sid]: https://packages.debian.org/sid/rustc
The exception is for the Android platform, where a higher Rust version
The exception to this is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this
@@ -85,7 +112,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself.
For the example code using Winit with WebAssembly, check out the [web example]. For
For example code using Winit with WebAssembly, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
@@ -108,14 +135,14 @@ glue crate (prior to `0.28` it used
The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your
application's main entry point. If Cargo resolves multiple versions, they will
application's main entrypoint. If Cargo resolves multiple versions they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29 | `android-activity = "0.5"` |
| 0.29.1-beta | `android-activity = "0.5.0-beta.1"` |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
@@ -126,7 +153,7 @@ The recommended way to avoid a conflict with the glue version is to avoid explic
depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*`
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[lib]
@@ -134,14 +161,14 @@ name = "main"
crate-type = ["cdylib"]
```
All Android applications are based on an `Activity` subclass, and the
All Android applications are based on an `Activity` subclass and the
`android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag:
| Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
@@ -150,13 +177,40 @@ class. Your application _must_ specify the base class it needs via a feature fla
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For example, add this to Cargo.toml:
```toml
winit = { version = "0.29.1-beta", features = [ "android-native-activity" ] }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11.0"
```
And, for example, define an entry point for your library like this:
```rust
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));
let event_loop = EventLoopBuilder::with_user_event()
.with_android_app(app)
.build();
_main(event_loop);
}
```
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.4", features = [ "android-native-activity" ] }`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.1-beta", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
@@ -167,13 +221,13 @@ doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::Resumed`.
`Event::NewEvents(StartCause::Init)`.
#### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
inside `Event::Resumed`.
by all UI related code, see issue [#1705]. You should consider creating your windows
inside `Event::NewEvents(StartCause::Init)`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
@@ -183,5 +237,5 @@ inside `Event::Resumed`.
#### Redox OS
Redox OS has some functionality not yet present that will be implemented when
Redox OS has some functionality not present yet, that will be implemented when
its orbital display server provides it.

View File

@@ -6,7 +6,6 @@ disallowed-methods = [
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },

View File

@@ -34,7 +34,13 @@ skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392
{ 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
{ name = "foreign-types-shared" }, # https://github.com/servo/core-foundation-rs/issues/634
{ name = "foreign-types" }, # See above ^, waiting for release
]
skip-tree = []

View File

@@ -5,7 +5,7 @@ These images are used in the documentation of `winit`.
## keyboard_*.svg
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
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.

View File

@@ -10,7 +10,7 @@ use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, NamedKey},
keyboard::Key,
window::WindowBuilder,
};
@@ -88,7 +88,7 @@ fn main() -> Result<(), impl std::error::Error> {
request_redraw = !request_redraw;
println!("\nrequest_redraw: {request_redraw}\n");
}
Key::Named(NamedKey::Escape) => {
Key::Escape => {
close_requested = true;
}
_ => (),

View File

@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState, NamedKey},
keyboard::{Key, ModifiersState},
window::{CursorGrabMode, WindowBuilder},
};
@@ -35,7 +35,7 @@ fn main() -> Result<(), impl std::error::Error> {
..
} => {
let result = match key {
Key::Named(NamedKey::Escape) => {
Key::Escape => {
elwt.exit();
Ok(())
}

View File

@@ -1,92 +0,0 @@
#![allow(clippy::single_match, clippy::disallowed_methods)]
#[cfg(not(wasm_platform))]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::{CustomCursor, WindowBuilder},
};
fn decode_cursor(bytes: &[u8]) -> CustomCursor {
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
let samples = img.into_flat_samples();
let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16);
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
}
#[cfg(not(wasm_platform))]
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
#[cfg(not(wasm_platform))]
SimpleLogger::new()
.with_level(log::LevelFilter::Info)
.init()
.unwrap();
#[cfg(wasm_platform)]
console_log::init_with_level(log::Level::Debug).unwrap();
let event_loop = EventLoop::new().unwrap();
let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(wasm_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
let mut cursor_idx = 0;
let mut cursor_visible = true;
let custom_cursors = [
decode_cursor(include_bytes!("data/cross.png")),
decode_cursor(include_bytes!("data/cross2.png")),
];
event_loop.run(move |event, _elwt| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: key,
..
},
..
} => match key.as_ref() {
Key::Character("1") => {
log::debug!("Setting cursor to {:?}", cursor_idx);
window.set_custom_cursor(&custom_cursors[cursor_idx]);
cursor_idx = (cursor_idx + 1) % 2;
}
Key::Character("2") => {
log::debug!("Setting cursor icon to default");
window.set_cursor_icon(Default::default());
}
Key::Character("3") => {
cursor_visible = !cursor_visible;
log::debug!("Setting cursor visibility to {:?}", cursor_visible);
window.set_cursor_visible(cursor_visible);
}
_ => {}
},
WindowEvent::RedrawRequested => {
#[cfg(not(wasm_platform))]
fill::fill_window(&window);
}
WindowEvent::CloseRequested => {
#[cfg(not(wasm_platform))]
_elwt.exit();
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => {}
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

View File

@@ -1,56 +0,0 @@
#![allow(clippy::single_match)]
//! Example for focusing a window.
use simple_logger::SimpleLogger;
#[cfg(not(wasm_platform))]
use std::time;
#[cfg(wasm_platform)]
use web_time as time;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
let mut deadline = time::Instant::now() + time::Duration::from_secs(3);
event_loop.run(move |event, elwt| {
match event {
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
// Timeout reached; focus the window.
println!("Re-focusing the window.");
deadline += time::Duration::from_secs(3);
window.focus_window();
}
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => (),
}
elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline));
})
}

View File

@@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::{Key, NamedKey};
use winit::keyboard::Key;
use winit::window::{Fullscreen, WindowBuilder};
#[cfg(target_os = "macos")]
@@ -65,7 +65,7 @@ fn main() -> Result<(), impl std::error::Error> {
},
..
} => match key {
Key::Named(NamedKey::Escape) => elwt.exit(),
Key::Escape => elwt.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() {

View File

@@ -6,7 +6,7 @@ use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, Ime, WindowEvent},
event_loop::EventLoop,
keyboard::NamedKey,
keyboard::{Key, KeyCode},
window::{ImePurpose, WindowBuilder},
};
@@ -69,12 +69,12 @@ fn main() -> Result<(), impl std::error::Error> {
WindowEvent::KeyboardInput { event, .. } => {
println!("key: {event:?}");
if event.state == ElementState::Pressed && event.logical_key == NamedKey::F2 {
if event.state == ElementState::Pressed && event.physical_key == KeyCode::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 == NamedKey::F3 {
if event.state == ElementState::Pressed && event.logical_key == Key::F3 {
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,

View File

@@ -9,7 +9,7 @@ fn main() -> Result<(), impl std::error::Error> {
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState, NamedKey},
keyboard::{Key, ModifiersState},
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
};
@@ -65,17 +65,17 @@ fn main() -> Result<(), impl std::error::Error> {
},
..
} => {
use NamedKey::{ArrowLeft, ArrowRight};
use Key::{ArrowLeft, ArrowRight};
window.set_title(&format!("{key:?}"));
let state = !modifiers.shift_key();
match key {
// Cycle through video modes
Key::Named(ArrowRight) | Key::Named(ArrowLeft) => {
if key == ArrowLeft {
video_mode_id = video_mode_id.saturating_sub(1);
} else if key == ArrowRight {
video_mode_id = (video_modes.len() - 1).min(video_mode_id + 1);
}
Key::ArrowRight | Key::ArrowLeft => {
video_mode_id = match key {
ArrowLeft => video_mode_id.saturating_sub(1),
ArrowRight => (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.
@@ -185,7 +185,7 @@ fn main() -> Result<(), impl std::error::Error> {
event:
KeyEvent {
state: ElementState::Released,
logical_key: Key::Named(NamedKey::Escape),
logical_key: Key::Escape,
..
},
..

View File

@@ -4,9 +4,9 @@ use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, NamedKey},
keyboard::Key,
window::Window,
};
@@ -40,18 +40,19 @@ fn main() -> Result<(), impl std::error::Error> {
}
}
WindowEvent::KeyboardInput {
event,
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: Key::Character(c),
..
},
is_synthetic: false,
..
} if event.state == ElementState::Pressed => match event.logical_key {
Key::Named(NamedKey::Escape) => elwt.exit(),
Key::Character(c) if c == "n" || c == "N" => {
let window = Window::new(elwt).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
_ => (),
},
} if matches!(c.as_ref(), "n" | "N") => {
let window = Window::new(elwt).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);

View File

@@ -5,7 +5,7 @@ use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
keyboard::KeyCode,
window::WindowBuilder,
};
@@ -34,7 +34,7 @@ fn main() -> Result<(), impl std::error::Error> {
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: PhysicalKey::Code(KeyCode::Space),
physical_key: KeyCode::Space,
state: ElementState::Released,
..
},

View File

@@ -11,6 +11,7 @@ mod example {
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::Key;
use winit::platform::startup_notify::{
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
};
@@ -45,7 +46,7 @@ mod example {
},
..
} => {
if logical_key == "n" {
if logical_key == Key::Character("n".into()) {
if let Some(window) = windows.get(&window_id) {
// Request a new activation token on this window.
// Once we get it we will use it to create a window.

View File

@@ -53,13 +53,6 @@ pub(super) fn fill_window(window: &Window) {
}
GC.with(|gc| {
let size = window.inner_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc
@@ -68,9 +61,13 @@ pub(super) fn fill_window(window: &Window) {
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xFF181818;
let size = window.inner_size();
surface
.resize(width, height)
.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

View File

@@ -3,7 +3,7 @@
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
keyboard::KeyCode,
window::{Fullscreen, WindowBuilder},
};
@@ -39,13 +39,13 @@ pub fn main() -> Result<(), impl std::error::Error> {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(c),
physical_key: KeyCode::KeyF,
state: ElementState::Released,
..
},
..
},
} if window_id == window.id() && c == "f" => {
} if window_id == window.id() => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {

View File

@@ -7,7 +7,7 @@ use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::{Key, KeyCode, PhysicalKey},
keyboard::{Key, KeyCode},
window::{Fullscreen, WindowBuilder},
};
@@ -51,14 +51,14 @@ fn main() -> Result<(), impl std::error::Error> {
}),
..
} => match physical_key {
PhysicalKey::Code(KeyCode::KeyM) => {
KeyCode::KeyM => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
window.focus_window();
}
}
PhysicalKey::Code(KeyCode::KeyV) => {
KeyCode::KeyV => {
if !visible {
visible = !visible;
window.set_visible(visible);

View File

@@ -2,9 +2,9 @@ use log::debug;
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::NamedKey,
keyboard::Key,
window::WindowBuilder,
};
@@ -27,10 +27,15 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput { event, .. }
if event.logical_key == NamedKey::Space
&& event.state == ElementState::Released =>
{
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Space,
state: ElementState::Released,
..
},
..
} => {
has_increments = !has_increments;
let new_increments = match window.resize_increments() {

View File

@@ -9,7 +9,7 @@ use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, NamedKey},
keyboard::Key,
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
window::{Window, WindowBuilder},
};
@@ -70,10 +70,10 @@ fn main() -> Result<(), impl std::error::Error> {
Key::Character("w") => {
let _ = windows.remove(&window_id);
}
Key::Named(NamedKey::ArrowRight) => {
Key::ArrowRight => {
windows.get(&window_id).unwrap().select_next_tab();
}
Key::Named(NamedKey::ArrowLeft) => {
Key::ArrowLeft => {
windows.get(&window_id).unwrap().select_previous_tab();
}
Key::Character(ch) => {

View File

@@ -1,198 +0,0 @@
use core::fmt;
use std::{error::Error, sync::Arc};
use crate::platform_impl::PlatformCustomCursor;
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
/// Use a custom image as a cursor (mouse pointer).
///
/// ## Platform-specific
///
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
///
/// # Example
///
/// ```
/// use winit::window::CustomCursor;
///
/// let w = 10;
/// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize];
/// let custom_cursor = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
///
/// #[cfg(target_family = "wasm")]
/// let custom_cursor_url = {
/// use winit::platform::web::CustomCursorExtWebSys;
/// CustomCursor::from_url("http://localhost:3000/cursor.png", 0, 0).unwrap()
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomCursor {
pub(crate) inner: Arc<PlatformCustomCursor>,
}
impl CustomCursor {
/// Creates a new cursor from an rgba buffer.
///
/// ## Platform-specific
///
/// - **Web:** Setting cursor could be delayed due to the creation of `Blob` objects,
/// which are async by nature.
pub fn from_rgba(
rgba: impl Into<Vec<u8>>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
Ok(Self {
inner: PlatformCustomCursor::from_rgba(
rgba.into(),
width,
height,
hotspot_x,
hotspot_y,
)?
.into(),
})
}
}
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone)]
pub enum BadImage {
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
/// guarantee that the cursor will work, but should avoid many platform and device specific
/// limits.
TooLarge { width: u16, height: u16 },
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
ByteCountNotDivisibleBy4 { byte_count: usize },
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
DimensionsVsPixelCount {
width: u16,
height: u16,
width_x_height: u64,
pixel_count: u64,
},
/// Produced when the hotspot is outside the image bounds
HotspotOutOfBounds {
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
},
}
impl fmt::Display for BadImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadImage::TooLarge { width, height } => write!(f,
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
),
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
),
BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => write!(f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
),
BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
} => write!(f,
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds ({width:?}x{height:?}).",
),
}
}
}
impl Error for BadImage {}
/// Platforms export this directly as `PlatformCustomCursor` if they need to only work with images.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CursorImage {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u16,
pub(crate) height: u16,
pub(crate) hotspot_x: u16,
pub(crate) hotspot_y: u16,
}
#[allow(dead_code)]
impl CursorImage {
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
return Err(BadImage::TooLarge { width, height });
}
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadImage::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
let width_x_height = width as u64 * height as u64;
if pixel_count != width_x_height {
return Err(BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
});
}
if hotspot_x >= width || hotspot_y >= height {
return Err(BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
});
}
Ok(CursorImage {
rgba,
width,
height,
hotspot_x,
hotspot_y,
})
}
}
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct NoCustomCursor;
#[allow(dead_code)]
impl NoCustomCursor {
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self)
}
}

View File

@@ -4,13 +4,13 @@
//!
//! Modern computer screens don't have a consistent relationship between resolution and size.
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
//! desktop nor mobile screens have consistent resolutions within their own size classes - common
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
//! and beyond.
//!
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
@@ -25,12 +25,12 @@
//!
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
//! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
//!
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
//! usually a mistake since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
//! than any DPI-dependent units.
//!
//! ### Position and Size types
@@ -42,11 +42,11 @@
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
//!
//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
//! will truncate the fractional part of the float rather than properly round to the nearest
//! will truncate the fractional part of the float, rather than properly round to the nearest
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! int, although rounding lessens the problem.
@@ -55,35 +55,34 @@
//!
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
//! can be found by calling [`window.scale_factor()`].
//!
//! ## How is the scale factor calculated?
//!
//! The scale factor is calculated differently on different platforms:
//! Scale factor is calculated differently on different platforms:
//!
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
//! display settings. While users are free to select any option they want, they're only given a
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
//! displays. When available, the user may pick a per-monitor scaling factor from a set of
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
//! but the specific value varies across devices.
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
//! the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present.
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
//! + If not present, use the value set in `Xft.dpi` in Xresources.
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//!
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
//! monitor scale factor may differ from the window scale factor.
//! - **Wayland:** Scale factor is suggested by the the compositor.
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.

View File

@@ -671,7 +671,7 @@ pub enum DeviceEvent {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RawKeyEvent {
pub physical_key: keyboard::PhysicalKey,
pub physical_key: keyboard::KeyCode,
pub state: ElementState,
}
@@ -703,7 +703,7 @@ pub struct KeyEvent {
/// `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::PhysicalKey,
pub physical_key: keyboard::KeyCode,
// Allowing `broken_intra_doc_links` for `logical_key`, because
// `key_without_modifiers` is not available on all platforms
@@ -743,7 +743,7 @@ pub struct KeyEvent {
/// 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::Named(NamedKey::Enter)`, this field is `Some("\r")`.
/// `logical_key` is `Key::Enter`, this field is `Some("\r")`.
///
/// This is `None` if the current keypress cannot
/// be interpreted as text.

View File

@@ -9,8 +9,6 @@
//! handle events.
use std::marker::PhantomData;
use std::ops::Deref;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{error, fmt};
@@ -224,22 +222,14 @@ impl<T> EventLoop<T> {
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
/// and any values not passed to this function will *not* be dropped.
///
/// Web applications are recommended to use
#[cfg_attr(
wasm_platform,
doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]"
)]
#[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")]
/// [^1] instead of [`run()`] to avoid the need
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
/// for the Javascript exception trick, and to make it clearer that the event loop runs
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
/// current thread of execution like it does on other platforms.
///
/// This function won't be available with `target_feature = "exception-handling"`.
///
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
/// [`run()`]: Self::run()
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM.
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
#[inline]
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
@@ -266,40 +256,12 @@ impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(&**self)
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl<T> AsFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl<T> AsRawFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
impl<T> Deref for EventLoop<T> {
type Target = EventLoopWindowTarget<T>;
fn deref(&self) -> &EventLoopWindowTarget<T> {
@@ -383,7 +345,7 @@ impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05()
}

View File

@@ -49,7 +49,11 @@ impl fmt::Display for BadIcon {
}
}
impl Error for BadIcon {}
impl Error for BadIcon {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RgbaIcon {

View File

@@ -185,112 +185,26 @@ impl std::fmt::Debug for NativeKey {
}
}
impl From<NativeKeyCode> for NativeKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
match code {
NativeKeyCode::Unidentified => NativeKey::Unidentified,
NativeKeyCode::Android(x) => NativeKey::Android(x),
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
}
}
}
impl PartialEq<NativeKey> for NativeKeyCode {
#[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
NativeKey::from(*self) == *rhs
}
}
impl PartialEq<NativeKeyCode> for NativeKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
rhs == self
}
}
/// Represents the location of a physical key.
///
/// This type is a superset of [`KeyCode`], including an [`Unidentified`](Self::Unidentified)
/// variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PhysicalKey {
/// A known key code
Code(KeyCode),
/// This variant is used when the key cannot be translated to a [`KeyCode`]
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
}
impl From<KeyCode> for PhysicalKey {
#[inline]
fn from(code: KeyCode) -> Self {
PhysicalKey::Code(code)
}
}
impl From<NativeKeyCode> for PhysicalKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
PhysicalKey::Unidentified(code)
}
}
impl PartialEq<KeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &KeyCode) -> bool {
match self {
PhysicalKey::Code(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for KeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
impl PartialEq<NativeKeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
match self {
PhysicalKey::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for NativeKeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
/// Code representing the location of a physical key
///
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
/// exceptions:
/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and
/// "SuperRight" here.
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
///
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyCode {
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`KeyCode`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
/// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave.
/// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd>
/// (hankaku/zenkaku/kanji) key on Japanese keyboards
@@ -734,7 +648,7 @@ pub enum KeyCode {
F35,
}
/// A [`Key::Named`] value
/// Key represents the meaning of a keypress.
///
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few
/// exceptions:
@@ -742,12 +656,32 @@ pub enum KeyCode {
/// another key which the specification calls `Super`. That does not exist here.)
/// - The `Space` variant here, can be identified by the character it generates in the
/// specificaiton.
/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`.
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
/// dead-key twice.
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NamedKey {
pub enum Key<Str = SmolStr> {
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
/// The `Alt` (Alternative) key.
///
/// This key enables the alternate modifier function for interpreting concurrent or subsequent
@@ -1451,131 +1385,83 @@ pub enum NamedKey {
F35,
}
/// Key represents the meaning of a keypress.
///
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
/// additions:
/// - All simple variants are wrapped under the `Named` variant
/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`.
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
/// dead-key twice.
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> {
/// A simple (unparameterised) action
Named(NamedKey),
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
}
impl From<NamedKey> for Key {
#[inline]
fn from(action: NamedKey) -> Self {
Key::Named(action)
}
}
impl From<NativeKey> for Key {
#[inline]
fn from(code: NativeKey) -> Self {
Key::Unidentified(code)
}
}
impl<Str> PartialEq<NamedKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NamedKey) -> bool {
match self {
Key::Named(ref a) => a == rhs,
_ => false,
macro_rules! map_match {
(
$to_match:expr,
// Custom match arms
{ $( $from:pat => $to:expr ),* },
// The enum's name
$prefix:path,
// Trivial match arms for unit variants
{ $( $t:tt ),* }) => {
match $to_match {
$( $from => $to, )*
$( Key::$t => Key::$t, )*
}
}
}
impl<Str: PartialEq<str>> PartialEq<str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &str) -> bool {
match self {
Key::Character(ref s) => s == rhs,
_ => false,
}
}
}
impl<Str: PartialEq<str>> PartialEq<&str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &&str) -> bool {
self == *rhs
}
}
impl<Str> PartialEq<NativeKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
match self {
Key::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl<Str> PartialEq<Key<Str>> for NativeKey {
#[inline]
fn eq(&self, rhs: &Key<Str>) -> bool {
rhs == self
}
};
}
impl Key<SmolStr> {
/// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
/// `Key`. All other variants remain unchanged.
pub fn as_ref(&self) -> Key<&str> {
match self {
Key::Named(a) => Key::Named(*a),
Key::Character(ch) => Key::Character(ch.as_str()),
Key::Dead(d) => Key::Dead(*d),
Key::Unidentified(u) => Key::Unidentified(u.clone()),
}
}
}
impl NamedKey {
/// Convert an action to its approximate textual equivalent.
///
/// # Examples
///
/// ```
/// use winit::keyboard::NamedKey;
///
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
/// assert_eq!(NamedKey::F20.to_text(), None);
/// ```
pub fn to_text(&self) -> Option<&str> {
match self {
NamedKey::Enter => Some("\r"),
NamedKey::Backspace => Some("\x08"),
NamedKey::Tab => Some("\t"),
NamedKey::Space => Some(" "),
NamedKey::Escape => Some("\x1b"),
_ => None,
}
map_match!(
self,
{
Key::Character(ch) => Key::Character(ch.as_str()),
Key::Dead(d) => Key::Dead(*d),
Key::Unidentified(u) => Key::Unidentified(u.clone())
},
Key,
{
Alt, AltGraph, CapsLock, Control, Fn, FnLock, NumLock, ScrollLock, Shift, Symbol,
SymbolLock, Meta, Hyper, Super, Enter, Tab, Space, ArrowDown, ArrowLeft,
ArrowRight, ArrowUp, End, Home, PageDown, PageUp, Backspace, Clear, Copy, CrSel,
Cut, Delete, EraseEof, ExSel, Insert, Paste, Redo, Undo, Accept, Again, Attn,
Cancel, ContextMenu, Escape, Execute, Find, Help, Pause, Play, Props, Select,
ZoomIn, ZoomOut, BrightnessDown, BrightnessUp, Eject, LogOff, Power, PowerOff,
PrintScreen, Hibernate, Standby, WakeUp, AllCandidates, Alphanumeric, CodeInput,
Compose, Convert, FinalMode, GroupFirst, GroupLast, GroupNext, GroupPrevious,
ModeChange, NextCandidate, NonConvert, PreviousCandidate, Process, SingleCandidate,
HangulMode, HanjaMode, JunjaMode, Eisu, Hankaku, Hiragana, HiraganaKatakana,
KanaMode, KanjiMode, Katakana, Romaji, Zenkaku, ZenkakuHankaku, Soft1, Soft2,
Soft3, Soft4, ChannelDown, ChannelUp, Close, MailForward, MailReply, MailSend,
MediaClose, MediaFastForward, MediaPause, MediaPlay, MediaPlayPause, MediaRecord,
MediaRewind, MediaStop, MediaTrackNext, MediaTrackPrevious, New, Open, Print, Save,
SpellCheck, Key11, Key12, AudioBalanceLeft, AudioBalanceRight, AudioBassBoostDown,
AudioBassBoostToggle, AudioBassBoostUp, AudioFaderFront, AudioFaderRear,
AudioSurroundModeNext, AudioTrebleDown, AudioTrebleUp, AudioVolumeDown,
AudioVolumeUp, AudioVolumeMute, MicrophoneToggle, MicrophoneVolumeDown,
MicrophoneVolumeUp, MicrophoneVolumeMute, SpeechCorrectionList, SpeechInputToggle,
LaunchApplication1, LaunchApplication2, LaunchCalendar, LaunchContacts, LaunchMail,
LaunchMediaPlayer, LaunchMusicPlayer, LaunchPhone, LaunchScreenSaver,
LaunchSpreadsheet, LaunchWebBrowser, LaunchWebCam, LaunchWordProcessor,
BrowserBack, BrowserFavorites, BrowserForward, BrowserHome, BrowserRefresh,
BrowserSearch, BrowserStop, AppSwitch, Call, Camera, CameraFocus, EndCall, GoBack,
GoHome, HeadsetHook, LastNumberRedial, Notification, MannerMode, VoiceDial, TV,
TV3DMode, TVAntennaCable, TVAudioDescription, TVAudioDescriptionMixDown,
TVAudioDescriptionMixUp, TVContentsMenu, TVDataService, TVInput, TVInputComponent1,
TVInputComponent2, TVInputComposite1, TVInputComposite2, TVInputHDMI1,
TVInputHDMI2, TVInputHDMI3, TVInputHDMI4, TVInputVGA1, TVMediaContext, TVNetwork,
TVNumberEntry, TVPower, TVRadioService, TVSatellite, TVSatelliteBS, TVSatelliteCS,
TVSatelliteToggle, TVTerrestrialAnalog, TVTerrestrialDigital, TVTimer, AVRInput,
AVRPower, ColorF0Red, ColorF1Green, ColorF2Yellow, ColorF3Blue, ColorF4Grey,
ColorF5Brown, ClosedCaptionToggle, Dimmer, DisplaySwap, DVR, Exit, FavoriteClear0,
FavoriteClear1, FavoriteClear2, FavoriteClear3, FavoriteRecall0, FavoriteRecall1,
FavoriteRecall2, FavoriteRecall3, FavoriteStore0, FavoriteStore1, FavoriteStore2,
FavoriteStore3, Guide, GuideNextDay, GuidePreviousDay, Info, InstantReplay, Link,
ListProgram, LiveContent, Lock, MediaApps, MediaAudioTrack, MediaLast,
MediaSkipBackward, MediaSkipForward, MediaStepBackward, MediaStepForward,
MediaTopMenu, NavigateIn, NavigateNext, NavigateOut, NavigatePrevious,
NextFavoriteChannel, NextUserProfile, OnDemand, Pairing, PinPDown, PinPMove,
PinPToggle, PinPUp, PlaySpeedDown, PlaySpeedReset, PlaySpeedUp, RandomToggle,
RcLowBattery, RecordSpeedNext, RfBypass, ScanChannelsToggle, ScreenModeNext,
Settings, SplitScreenToggle, STBInput, STBPower, Subtitle, Teletext, VideoModeNext,
Wink, ZoomToggle, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15,
F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31,
F32, F33, F34, F35
}
)
}
}
@@ -1585,16 +1471,20 @@ impl Key {
/// # Examples
///
/// ```
/// use winit::keyboard::{NamedKey, Key};
/// use winit::keyboard::Key;
///
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
/// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
/// assert_eq!(Key::Enter.to_text(), Some("\r"));
/// assert_eq!(Key::F20.to_text(), None);
/// ```
pub fn to_text(&self) -> Option<&str> {
match self {
Key::Named(action) => action.to_text(),
Key::Character(ch) => Some(ch.as_str()),
Key::Enter => Some("\r"),
Key::Backspace => Some("\x08"),
Key::Tab => Some("\t"),
Key::Space => Some(" "),
Key::Escape => Some("\x1b"),
_ => None,
}
}
@@ -1682,7 +1572,7 @@ bitflags! {
/// Represents the current state of the keyboard modifiers
///
/// Each flag represents a modifier and is set if this modifier is active.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModifiersState: u32 {
/// The "shift" key.
const SHIFT = 0b100;

View File

@@ -10,12 +10,12 @@
//! let event_loop = EventLoop::new().unwrap();
//! ```
//!
//! Once this is done, there are two ways to create a [`Window`]:
//! Once this is done there are two ways to create a [`Window`]:
//!
//! - Calling [`Window::new(&event_loop)`][window_new].
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
//!
//! The first method is the simplest and will give you default values for everything. The second
//! The first method is the simplest, and will give you default values for everything. The second
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
//!
@@ -26,37 +26,17 @@
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
//!
//! You can retrieve events by calling [`EventLoop::run()`]. This function will
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//! will run until [`exit()`] is used, at which point [`Event`]`::`[`LoopExiting`].
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
//! most other platforms. However, this model can be re-implemented to an extent with
#![cfg_attr(
any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
),
doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]"
)]
#![cfg_attr(
not(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
)),
doc = "`EventLoopExtPumpEvents::pump_events()`"
)]
//! [^1]. See that method's documentation for more reasons about why
//! it's discouraged beyond compatibility reasons.
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond compatibility reasons.
//!
//!
//! ```no_run
@@ -92,9 +72,9 @@
//!
//! // Queue a RedrawRequested event.
//! //
//! // You only need to call this if you've determined that you need to redraw in
//! // You only need to call this if you've determined that you need to redraw, in
//! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead.
//! // can just render here instead.
//! window.request_redraw();
//! },
//! Event::WindowEvent {
@@ -112,16 +92,16 @@
//! });
//! ```
//!
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`] to determine which [`Window`]
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
//! dispatched the event.
//!
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//!
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
@@ -130,8 +110,9 @@
//! window visible only once you're ready to render into it.
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [`EventLoop::run()`]: event_loop::EventLoop::run
//! [event_loop_run]: event_loop::EventLoop::run
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
@@ -139,14 +120,15 @@
//! [window_new]: window::Window::new
//! [window_builder_new]: window::WindowBuilder::new
//! [window_builder_build]: window::WindowBuilder::build
//! [`Window::id()`]: window::Window::id
//! [window_id_fn]: window::Window::id
//! [`Event`]: event::Event
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`Event::LoopExiting`]: event::Event::LoopExiting
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopExiting`]: event::Event::LoopExiting
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland.
#![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
@@ -172,7 +154,6 @@ extern crate bitflags;
pub mod dpi;
#[macro_use]
pub mod error;
mod cursor;
pub mod event;
pub mod event_loop;
mod icon;
@@ -182,18 +163,3 @@ mod platform_impl;
pub mod window;
pub mod platform;
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
/// and `Sync`, since they always execute on a single thread.
///
/// # Safety
///
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
/// The objects could be sent across the threads, but once passed to winit, they execute on the
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
#[doc(hidden)]
#[derive(Clone, Debug)]
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
unsafe impl<T> Send for SendSyncWrapper<T> {}
unsafe impl<T> Sync for SendSyncWrapper<T> {}

View File

@@ -138,18 +138,14 @@ impl MonitorHandle {
self.inner.refresh_rate_millihertz()
}
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`].
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](crate::dpi) module for more information.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0.
///
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()

View File

@@ -84,10 +84,5 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// use winit::platform::android::activity::AndroidApp;
/// ```
pub mod activity {
// We enable the `"native-activity"` feature just so that we can build the
// docs, but it'll be very confusing for users to see the docs with that
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
pub use android_activity::*;
}

View File

@@ -69,25 +69,11 @@ pub trait WindowExtIOS {
///
/// The default is to prefer showing the status bar.
///
/// This sets the value of the
/// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc)
/// property.
///
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
/// This changes the value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
fn set_prefers_status_bar_hidden(&self, hidden: bool);
/// Sets the preferred status bar style for the [`Window`].
///
/// The default is system-defined.
///
/// This sets the value of the
/// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
/// property.
///
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
}
impl WindowExtIOS for Window {
@@ -121,12 +107,6 @@ impl WindowExtIOS for Window {
self.window
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
}
#[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.window
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
}
}
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
@@ -174,14 +154,6 @@ pub trait WindowBuilderExtIOS {
/// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
/// Sets the style of the [`Window`]'s status bar.
///
/// The default is system-defined.
///
/// This sets the initial value returned by
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
}
impl WindowBuilderExtIOS for WindowBuilder {
@@ -215,12 +187,6 @@ impl WindowBuilderExtIOS for WindowBuilder {
self.platform_specific.prefers_status_bar_hidden = hidden;
self
}
#[inline]
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.platform_specific.preferred_status_bar_style = status_bar_style;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
@@ -298,11 +264,3 @@ bitflags! {
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum StatusBarStyle {
#[default]
Default,
LightContent,
DarkContent,
}

View File

@@ -35,9 +35,9 @@ pub trait EventLoopExtRunOnDemand {
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
/// - This extension isn't available on all platforms, since it's not always possible to
/// return to the caller (specifically this is impossible on iOS and Web - though with
/// the Web backend it is possible to use `spawn()` more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need
@@ -57,13 +57,8 @@ pub trait EventLoopExtRunOnDemand {
/// on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
///
#[cfg_attr(
not(wasm_platform),
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
)]
///
/// [`exit()`]: EventLoopWindowTarget::exit()
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow()
/// [`exit()`]: EventLoopWindowTarget::exit
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);

View File

@@ -1,14 +1,14 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::{KeyCode, PhysicalKey};
use crate::keyboard::KeyCode;
// TODO: Describe what this value contains for each platform
/// Additional methods for the [`PhysicalKey`] type that allow the user to access the platform-specific
/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific
/// scancode.
///
/// [`PhysicalKey`]: crate::keyboard::PhysicalKey
pub trait PhysicalKeyExtScancode {
/// [`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.
@@ -18,28 +18,13 @@ pub trait PhysicalKeyExtScancode {
/// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8.
fn to_scancode(self) -> Option<u32>;
/// Constructs a `PhysicalKey` from a platform-specific physical key identifier.
/// Constructs a `KeyCode` from a platform-specific physical key identifier.
///
/// Note that this conversion may be lossy, i.e. converting the returned `PhysicalKey` back
/// 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) -> PhysicalKey;
}
impl PhysicalKeyExtScancode for KeyCode
where
PhysicalKey: PhysicalKeyExtScancode,
{
#[inline]
fn from_scancode(scancode: u32) -> PhysicalKey {
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
}
#[inline]
fn to_scancode(self) -> Option<u32> {
<PhysicalKey as PhysicalKeyExtScancode>::to_scancode(PhysicalKey::Code(self))
}
fn from_scancode(scancode: u32) -> KeyCode;
}

View File

@@ -27,13 +27,10 @@
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
use crate::cursor::CustomCursor;
use crate::event::Event;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::PlatformCustomCursor;
use crate::window::{Window, WindowBuilder};
use crate::SendSyncWrapper;
use web_sys::HtmlCanvasElement;
@@ -84,22 +81,26 @@ pub trait WindowBuilderExtWebSys {
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = SendSyncWrapper(canvas);
self.platform_specific.canvas = canvas;
self
}
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.platform_specific.prevent_default = prevent_default;
self
}
fn with_focusable(mut self, focusable: bool) -> Self {
self.platform_specific.focusable = focusable;
self
}
fn with_append(mut self, append: bool) -> Self {
self.platform_specific.append = append;
self
}
}
@@ -111,28 +112,13 @@ pub trait EventLoopExtWebSys {
/// Initializes the winit event loop.
///
/// Unlike
#[cfg_attr(
all(wasm_platform, target_feature = "exception-handling"),
doc = "`run()`"
)]
#[cfg_attr(
not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type.
/// 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.
///
#[cfg_attr(
not(all(wasm_platform, target_feature = "exception-handling")),
doc = "[`run()`]: EventLoop::run()"
)]
/// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
@@ -202,25 +188,3 @@ pub enum PollStrategy {
#[default]
Scheduler,
}
pub trait CustomCursorExtWebSys {
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self;
}
impl CustomCursorExtWebSys for CustomCursor {
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Self {
Self {
inner: PlatformCustomCursor::Url {
url,
hotspot_x,
hotspot_y,
}
.into(),
}
}
}

View File

@@ -3,10 +3,10 @@ use android_activity::{
AndroidApp,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
PhysicalKey::Code(match keycode {
pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
match keycode {
Keycode::A => KeyCode::KeyA,
Keycode::B => KeyCode::KeyB,
Keycode::C => KeyCode::KeyC,
@@ -155,8 +155,8 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp,
keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
})
keycode => KeyCode::Unidentified(NativeKeyCode::Android(keycode.into())),
}
}
/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent
@@ -231,10 +231,10 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
None | Some(KeyMapChar::None) => match keycode {
// Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::Named(NamedKey::BrowserHome),
Back => Key::Named(NamedKey::BrowserBack),
Call => Key::Named(NamedKey::Call),
Endcall => Key::Named(NamedKey::EndCall),
Home => Key::BrowserHome,
Back => Key::BrowserBack,
Call => Key::Call,
Endcall => Key::EndCall,
//-------------------------------------------------------------------------------
// These should be redundant because they should have already been matched
@@ -291,81 +291,81 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
At => Key::Character("@".into()),
Plus => Key::Character("+".into()),
//-------------------------------------------------------------------------------
DpadUp => Key::Named(NamedKey::ArrowUp),
DpadDown => Key::Named(NamedKey::ArrowDown),
DpadLeft => Key::Named(NamedKey::ArrowLeft),
DpadRight => Key::Named(NamedKey::ArrowRight),
DpadCenter => Key::Named(NamedKey::Enter),
DpadUp => Key::ArrowUp,
DpadDown => Key::ArrowDown,
DpadLeft => Key::ArrowLeft,
DpadRight => Key::ArrowRight,
DpadCenter => Key::Enter,
VolumeUp => Key::Named(NamedKey::AudioVolumeUp),
VolumeDown => Key::Named(NamedKey::AudioVolumeDown),
Power => Key::Named(NamedKey::Power),
Camera => Key::Named(NamedKey::Camera),
Clear => Key::Named(NamedKey::Clear),
VolumeUp => Key::AudioVolumeUp,
VolumeDown => Key::AudioVolumeDown,
Power => Key::Power,
Camera => Key::Camera,
Clear => Key::Clear,
AltLeft => Key::Named(NamedKey::Alt),
AltRight => Key::Named(NamedKey::Alt),
ShiftLeft => Key::Named(NamedKey::Shift),
ShiftRight => Key::Named(NamedKey::Shift),
Tab => Key::Named(NamedKey::Tab),
Space => Key::Named(NamedKey::Space),
Sym => Key::Named(NamedKey::Symbol),
Explorer => Key::Named(NamedKey::LaunchWebBrowser),
Envelope => Key::Named(NamedKey::LaunchMail),
Enter => Key::Named(NamedKey::Enter),
Del => Key::Named(NamedKey::Backspace),
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::Named(NamedKey::Alt),
Num => Key::Alt,
Headsethook => Key::Named(NamedKey::HeadsetHook),
Focus => Key::Named(NamedKey::CameraFocus),
Headsethook => Key::HeadsetHook,
Focus => Key::CameraFocus,
Notification => Key::Named(NamedKey::Notification),
Search => Key::Named(NamedKey::BrowserSearch),
MediaPlayPause => Key::Named(NamedKey::MediaPlayPause),
MediaStop => Key::Named(NamedKey::MediaStop),
MediaNext => Key::Named(NamedKey::MediaTrackNext),
MediaPrevious => Key::Named(NamedKey::MediaTrackPrevious),
MediaRewind => Key::Named(NamedKey::MediaRewind),
MediaFastForward => Key::Named(NamedKey::MediaFastForward),
Mute => Key::Named(NamedKey::MicrophoneVolumeMute),
PageUp => Key::Named(NamedKey::PageUp),
PageDown => Key::Named(NamedKey::PageDown),
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,
Escape => Key::Named(NamedKey::Escape),
ForwardDel => Key::Named(NamedKey::Delete),
CtrlLeft => Key::Named(NamedKey::Control),
CtrlRight => Key::Named(NamedKey::Control),
CapsLock => Key::Named(NamedKey::CapsLock),
ScrollLock => Key::Named(NamedKey::ScrollLock),
MetaLeft => Key::Named(NamedKey::Super),
MetaRight => Key::Named(NamedKey::Super),
Function => Key::Named(NamedKey::Fn),
Sysrq => Key::Named(NamedKey::PrintScreen),
Break => Key::Named(NamedKey::Pause),
MoveHome => Key::Named(NamedKey::Home),
MoveEnd => Key::Named(NamedKey::End),
Insert => Key::Named(NamedKey::Insert),
Forward => Key::Named(NamedKey::BrowserForward),
MediaPlay => Key::Named(NamedKey::MediaPlay),
MediaPause => Key::Named(NamedKey::MediaPause),
MediaClose => Key::Named(NamedKey::MediaClose),
MediaEject => Key::Named(NamedKey::Eject),
MediaRecord => Key::Named(NamedKey::MediaRecord),
F1 => Key::Named(NamedKey::F1),
F2 => Key::Named(NamedKey::F2),
F3 => Key::Named(NamedKey::F3),
F4 => Key::Named(NamedKey::F4),
F5 => Key::Named(NamedKey::F5),
F6 => Key::Named(NamedKey::F6),
F7 => Key::Named(NamedKey::F7),
F8 => Key::Named(NamedKey::F8),
F9 => Key::Named(NamedKey::F9),
F10 => Key::Named(NamedKey::F10),
F11 => Key::Named(NamedKey::F11),
F12 => Key::Named(NamedKey::F12),
NumLock => Key::Named(NamedKey::NumLock),
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()),
@@ -382,97 +382,97 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Named(NamedKey::Enter),
NumpadEnter => Key::Enter,
NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()),
VolumeMute => Key::Named(NamedKey::AudioVolumeMute),
Info => Key::Named(NamedKey::Info),
ChannelUp => Key::Named(NamedKey::ChannelUp),
ChannelDown => Key::Named(NamedKey::ChannelDown),
ZoomIn => Key::Named(NamedKey::ZoomIn),
ZoomOut => Key::Named(NamedKey::ZoomOut),
Tv => Key::Named(NamedKey::TV),
Guide => Key::Named(NamedKey::Guide),
Dvr => Key::Named(NamedKey::DVR),
Bookmark => Key::Named(NamedKey::BrowserFavorites),
Captions => Key::Named(NamedKey::ClosedCaptionToggle),
Settings => Key::Named(NamedKey::Settings),
TvPower => Key::Named(NamedKey::TVPower),
TvInput => Key::Named(NamedKey::TVInput),
StbPower => Key::Named(NamedKey::STBPower),
StbInput => Key::Named(NamedKey::STBInput),
AvrPower => Key::Named(NamedKey::AVRPower),
AvrInput => Key::Named(NamedKey::AVRInput),
ProgRed => Key::Named(NamedKey::ColorF0Red),
ProgGreen => Key::Named(NamedKey::ColorF1Green),
ProgYellow => Key::Named(NamedKey::ColorF2Yellow),
ProgBlue => Key::Named(NamedKey::ColorF3Blue),
AppSwitch => Key::Named(NamedKey::AppSwitch),
LanguageSwitch => Key::Named(NamedKey::GroupNext),
MannerMode => Key::Named(NamedKey::MannerMode),
Keycode3dMode => Key::Named(NamedKey::TV3DMode),
Contacts => Key::Named(NamedKey::LaunchContacts),
Calendar => Key::Named(NamedKey::LaunchCalendar),
Music => Key::Named(NamedKey::LaunchMusicPlayer),
Calculator => Key::Named(NamedKey::LaunchApplication2),
ZenkakuHankaku => Key::Named(NamedKey::ZenkakuHankaku),
Eisu => Key::Named(NamedKey::Eisu),
Muhenkan => Key::Named(NamedKey::NonConvert),
Henkan => Key::Named(NamedKey::Convert),
KatakanaHiragana => Key::Named(NamedKey::HiraganaKatakana),
Kana => Key::Named(NamedKey::KanjiMode),
BrightnessDown => Key::Named(NamedKey::BrightnessDown),
BrightnessUp => Key::Named(NamedKey::BrightnessUp),
MediaAudioTrack => Key::Named(NamedKey::MediaAudioTrack),
Sleep => Key::Named(NamedKey::Standby),
Wakeup => Key::Named(NamedKey::WakeUp),
Pairing => Key::Named(NamedKey::Pairing),
MediaTopMenu => Key::Named(NamedKey::MediaTopMenu),
LastChannel => Key::Named(NamedKey::MediaLast),
TvDataService => Key::Named(NamedKey::TVDataService),
VoiceAssist => Key::Named(NamedKey::VoiceDial),
TvRadioService => Key::Named(NamedKey::TVRadioService),
TvTeletext => Key::Named(NamedKey::Teletext),
TvNumberEntry => Key::Named(NamedKey::TVNumberEntry),
TvTerrestrialAnalog => Key::Named(NamedKey::TVTerrestrialAnalog),
TvTerrestrialDigital => Key::Named(NamedKey::TVTerrestrialDigital),
TvSatellite => Key::Named(NamedKey::TVSatellite),
TvSatelliteBs => Key::Named(NamedKey::TVSatelliteBS),
TvSatelliteCs => Key::Named(NamedKey::TVSatelliteCS),
TvSatelliteService => Key::Named(NamedKey::TVSatelliteToggle),
TvNetwork => Key::Named(NamedKey::TVNetwork),
TvAntennaCable => Key::Named(NamedKey::TVAntennaCable),
TvInputHdmi1 => Key::Named(NamedKey::TVInputHDMI1),
TvInputHdmi2 => Key::Named(NamedKey::TVInputHDMI2),
TvInputHdmi3 => Key::Named(NamedKey::TVInputHDMI3),
TvInputHdmi4 => Key::Named(NamedKey::TVInputHDMI4),
TvInputComposite1 => Key::Named(NamedKey::TVInputComposite1),
TvInputComposite2 => Key::Named(NamedKey::TVInputComposite2),
TvInputComponent1 => Key::Named(NamedKey::TVInputComponent1),
TvInputComponent2 => Key::Named(NamedKey::TVInputComponent2),
TvInputVga1 => Key::Named(NamedKey::TVInputVGA1),
TvAudioDescription => Key::Named(NamedKey::TVAudioDescription),
TvAudioDescriptionMixUp => Key::Named(NamedKey::TVAudioDescriptionMixUp),
TvAudioDescriptionMixDown => Key::Named(NamedKey::TVAudioDescriptionMixDown),
TvZoomMode => Key::Named(NamedKey::ZoomToggle),
TvContentsMenu => Key::Named(NamedKey::TVContentsMenu),
TvMediaContextMenu => Key::Named(NamedKey::TVMediaContext),
TvTimerProgramming => Key::Named(NamedKey::TVTimer),
Help => Key::Named(NamedKey::Help),
NavigatePrevious => Key::Named(NamedKey::NavigatePrevious),
NavigateNext => Key::Named(NamedKey::NavigateNext),
NavigateIn => Key::Named(NamedKey::NavigateIn),
NavigateOut => Key::Named(NamedKey::NavigateOut),
MediaSkipForward => Key::Named(NamedKey::MediaSkipForward),
MediaSkipBackward => Key::Named(NamedKey::MediaSkipBackward),
MediaStepForward => Key::Named(NamedKey::MediaStepForward),
MediaStepBackward => Key::Named(NamedKey::MediaStepBackward),
Cut => Key::Named(NamedKey::Cut),
Copy => Key::Named(NamedKey::Copy),
Paste => Key::Named(NamedKey::Paste),
Refresh => Key::Named(NamedKey::BrowserRefresh),
VolumeMute => Key::AudioVolumeMute,
Info => Key::Info,
ChannelUp => Key::ChannelUp,
ChannelDown => Key::ChannelDown,
ZoomIn => Key::ZoomIn,
ZoomOut => Key::ZoomOut,
Tv => Key::TV,
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,
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,
Kana => Key::KanjiMode,
BrightnessDown => Key::BrightnessDown,
BrightnessUp => Key::BrightnessUp,
MediaAudioTrack => Key::MediaAudioTrack,
Sleep => Key::Standby,
Wakeup => Key::WakeUp,
Pairing => Key::Pairing,
MediaTopMenu => Key::MediaTopMenu,
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,
MediaSkipForward => Key::MediaSkipForward,
MediaSkipBackward => Key::MediaSkipBackward,
MediaStepForward => Key::MediaStepForward,
MediaStepBackward => Key::MediaStepBackward,
Cut => Key::Cut,
Copy => Key::Copy,
Paste => Key::Paste,
Refresh => Key::BrowserRefresh,
// -----------------------------------------------------------------
// Keycodes that don't have a logical Key mapping
@@ -555,10 +555,6 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native),
// It's always possible that new versions of Android could introduce
// key codes we can't know about at compile time.
_ => Key::Unidentified(native),
},
}
}

View File

@@ -18,7 +18,6 @@ use android_activity::{
use once_cell::sync::Lazy;
use crate::{
cursor::CustomCursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, Force, InnerSizeWriter, StartCause},
@@ -457,7 +456,7 @@ impl<T: 'static> EventLoop<T> {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
physical_key: keycodes::to_physical_keycode(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
@@ -907,8 +906,6 @@ impl Window {
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub fn set_custom_cursor(&self, _: CustomCursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
@@ -949,10 +946,10 @@ impl Window {
#[cfg(feature = "rwh_04")]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
use rwh_04::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
let mut handle = rwh_04::AndroidNdkHandle::empty();
handle.a_native_window = native_window.ptr().as_ptr() as *mut _;
rwh_04::RawWindowHandle::AndroidNdk(handle)
} else {
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
}
@@ -975,13 +972,10 @@ impl Window {
}
#[cfg(feature = "rwh_06")]
// Allow the usage of HasRawWindowHandle inside this function
#[allow(deprecated)]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
use rwh_06::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
let handle = rwh_06::AndroidNdkWindowHandle::new(native_window.ptr().cast());
Ok(rwh_06::RawWindowHandle::AndroidNdk(handle))
} else {
log::error!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
Err(rwh_06::HandleError::Unavailable)
@@ -1034,7 +1028,6 @@ impl Display for OsError {
}
}
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]

View File

@@ -200,10 +200,6 @@ impl AppState {
)
}
fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated)
}
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched {
@@ -247,7 +243,7 @@ impl AppState {
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() || self.has_terminated() {
if !self.has_launched() {
return None;
}
@@ -394,7 +390,7 @@ impl AppState {
}
fn events_cleared_transition(&mut self) {
if !self.has_launched() || self.has_terminated() {
if !self.has_launched() {
return;
}
let (waiting_event_handler, old) = match self.take_state() {
@@ -590,10 +586,6 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
events: I,
) {
let mut this = AppState::get_mut(mtm);
if this.has_terminated() {
return;
}
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
@@ -745,7 +737,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() {
if !this.has_launched() {
return;
}
match this.state_mut() {

View File

@@ -289,7 +289,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {} // may happen when running on macOS
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -304,7 +304,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {} // may happen when running on macOS
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}

View File

@@ -77,7 +77,6 @@ pub(crate) use self::{
};
use self::uikit::UIScreen;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View File

@@ -12,7 +12,6 @@ mod event;
mod responder;
mod screen;
mod screen_mode;
mod status_bar_style;
mod touch;
mod trait_collection;
mod view;
@@ -26,7 +25,6 @@ pub(crate) use self::event::UIEvent;
pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode;
pub(crate) use self::status_bar_style::UIStatusBarStyle;
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)]

View File

@@ -1,27 +0,0 @@
use crate::platform::ios::StatusBarStyle;
use icrate::Foundation::NSInteger;
use objc2::encode::{Encode, Encoding};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIStatusBarStyle {
#[default]
Default = 0,
LightContent = 1,
DarkContent = 3,
}
impl From<StatusBarStyle> for UIStatusBarStyle {
fn from(value: StatusBarStyle) -> Self {
match value {
StatusBarStyle::Default => Self::Default,
StatusBarStyle::LightContent => Self::LightContent,
StatusBarStyle::DarkContent => Self::DarkContent,
}
}
}
unsafe impl Encode for UIStatusBarStyle {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -11,8 +11,8 @@ use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, Cl
use super::app_state::{self, EventWrapper};
use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIResponder, UIStatusBarStyle, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView,
UIViewController, UIWindow,
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController,
UIWindow,
};
use super::window::WindowId;
use crate::{
@@ -267,7 +267,6 @@ impl WinitView {
pub struct ViewControllerState {
prefers_status_bar_hidden: Cell<bool>,
preferred_status_bar_style: Cell<UIStatusBarStyle>,
prefers_home_indicator_auto_hidden: Cell<bool>,
supported_orientations: Cell<UIInterfaceOrientationMask>,
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
@@ -298,7 +297,6 @@ declare_class!(
&mut this.state,
Box::new(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(
@@ -322,11 +320,6 @@ declare_class!(
self.state.prefers_status_bar_hidden.get()
}
#[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.state.preferred_status_bar_style.get()
}
#[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.state.prefers_home_indicator_auto_hidden.get()
@@ -352,11 +345,6 @@ impl WinitViewController {
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) {
self.state.preferred_status_bar_style.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.state.prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities();
@@ -415,8 +403,6 @@ impl WinitViewController {
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
this.set_preferred_status_bar_style(platform_attributes.preferred_status_bar_style.into());
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
this.set_prefers_home_indicator_auto_hidden(
@@ -487,7 +473,7 @@ impl WinitUIWindow {
this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.0.clone().map(Into::into) {
match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
let screen = monitor.ui_screen(mtm);

View File

@@ -11,12 +11,11 @@ use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
cursor::CustomCursor,
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform::ios::{ScreenEdge, ValidOrientations},
platform_impl::platform::{
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
},
@@ -178,10 +177,6 @@ impl Inner {
debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub fn set_custom_cursor(&self, _: CustomCursor) {
debug!("`Window::set_custom_cursor` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -427,7 +422,7 @@ impl Window {
// TODO: transparency, visible
let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.0.clone().map(Into::into);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
@@ -556,11 +551,6 @@ impl Inner {
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.view_controller.set_prefers_status_bar_hidden(hidden);
}
pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.view_controller
.set_preferred_status_bar_style(status_bar_style.into());
}
}
impl Inner {
@@ -669,6 +659,5 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
pub preferred_status_bar_style: StatusBarStyle,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
}

View File

@@ -1,18 +1,18 @@
//! Convert XKB keys to Winit keys.
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
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_physicalkey(keycode: u32) -> PhysicalKey {
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) -> PhysicalKey {
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,
@@ -21,8 +21,8 @@ pub fn scancode_to_keycode(scancode: u32) -> PhysicalKey {
// 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.
PhysicalKey::Code(match scancode {
0 => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(0)),
match scancode {
0 => KeyCode::Unidentified(NativeKeyCode::Xkb(0)),
1 => KeyCode::Escape,
2 => KeyCode::Digit1,
3 => KeyCode::Digit2,
@@ -256,7 +256,7 @@ pub fn scancode_to_keycode(scancode: u32) -> PhysicalKey {
// 237 => KeyCode::BLUETOOTH,
// 238 => KeyCode::WLAN,
// 239 => KeyCode::UWB,
240 => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
240 => KeyCode::Unidentified(NativeKeyCode::Unidentified),
// 241 => KeyCode::VIDEO_NEXT,
// 242 => KeyCode::VIDEO_PREV,
// 243 => KeyCode::BRIGHTNESS_CYCLE,
@@ -265,23 +265,14 @@ pub fn scancode_to_keycode(scancode: u32) -> PhysicalKey {
// 246 => KeyCode::WWAN,
// 247 => KeyCode::RFKILL,
// 248 => KeyCode::KEY_MICMUTE,
_ => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(scancode)),
})
_ => KeyCode::Unidentified(NativeKeyCode::Xkb(scancode)),
}
}
pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
let code = match key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(code) => {
return match code {
NativeKeyCode::Unidentified => Some(240),
NativeKeyCode::Xkb(raw) => Some(raw),
_ => None,
};
}
};
match code {
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),
@@ -424,213 +415,213 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
pub fn keysym_to_key(keysym: u32) -> Key {
use xkbcommon_dl::keysyms;
Key::Named(match keysym {
match keysym {
// TTY function keys
keysyms::BackSpace => NamedKey::Backspace,
keysyms::Tab => NamedKey::Tab,
// keysyms::Linefeed => NamedKey::Linefeed,
keysyms::Clear => NamedKey::Clear,
keysyms::Return => NamedKey::Enter,
keysyms::Pause => NamedKey::Pause,
keysyms::Scroll_Lock => NamedKey::ScrollLock,
keysyms::Sys_Req => NamedKey::PrintScreen,
keysyms::Escape => NamedKey::Escape,
keysyms::Delete => NamedKey::Delete,
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 => NamedKey::Compose,
keysyms::Codeinput => NamedKey::CodeInput,
keysyms::SingleCandidate => NamedKey::SingleCandidate,
keysyms::MultipleCandidate => NamedKey::AllCandidates,
keysyms::PreviousCandidate => NamedKey::PreviousCandidate,
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 => NamedKey::KanjiMode,
keysyms::Muhenkan => NamedKey::NonConvert,
keysyms::Henkan_Mode => NamedKey::Convert,
keysyms::Romaji => NamedKey::Romaji,
keysyms::Hiragana => NamedKey::Hiragana,
keysyms::Hiragana_Katakana => NamedKey::HiraganaKatakana,
keysyms::Zenkaku => NamedKey::Zenkaku,
keysyms::Hankaku => NamedKey::Hankaku,
keysyms::Zenkaku_Hankaku => NamedKey::ZenkakuHankaku,
// keysyms::Touroku => NamedKey::Touroku,
// keysyms::Massyo => NamedKey::Massyo,
keysyms::Kana_Lock => NamedKey::KanaMode,
keysyms::Kana_Shift => NamedKey::KanaMode,
keysyms::Eisu_Shift => NamedKey::Alphanumeric,
keysyms::Eisu_toggle => NamedKey::Alphanumeric,
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 => NamedKey::CodeInput,
// keysyms::Zen_Koho => NamedKey::AllCandidates,
// keysyms::Mae_Koho => NamedKey::PreviousCandidate,
// keysyms::Kanji_Bangou => Key::CodeInput,
// keysyms::Zen_Koho => Key::AllCandidates,
// keysyms::Mae_Koho => Key::PreviousCandidate,
// Cursor control & motion
keysyms::Home => NamedKey::Home,
keysyms::Left => NamedKey::ArrowLeft,
keysyms::Up => NamedKey::ArrowUp,
keysyms::Right => NamedKey::ArrowRight,
keysyms::Down => NamedKey::ArrowDown,
// keysyms::Prior => NamedKey::PageUp,
keysyms::Page_Up => NamedKey::PageUp,
// keysyms::Next => NamedKey::PageDown,
keysyms::Page_Down => NamedKey::PageDown,
keysyms::End => NamedKey::End,
// keysyms::Begin => NamedKey::Begin,
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 => NamedKey::Select,
keysyms::Print => NamedKey::PrintScreen,
keysyms::Execute => NamedKey::Execute,
keysyms::Insert => NamedKey::Insert,
keysyms::Undo => NamedKey::Undo,
keysyms::Redo => NamedKey::Redo,
keysyms::Menu => NamedKey::ContextMenu,
keysyms::Find => NamedKey::Find,
keysyms::Cancel => NamedKey::Cancel,
keysyms::Help => NamedKey::Help,
keysyms::Break => NamedKey::Pause,
keysyms::Mode_switch => NamedKey::ModeChange,
// keysyms::script_switch => NamedKey::ModeChange,
keysyms::Num_Lock => NamedKey::NumLock,
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 => return Key::Character(" "),
keysyms::KP_Tab => NamedKey::Tab,
keysyms::KP_Enter => NamedKey::Enter,
keysyms::KP_F1 => NamedKey::F1,
keysyms::KP_F2 => NamedKey::F2,
keysyms::KP_F3 => NamedKey::F3,
keysyms::KP_F4 => NamedKey::F4,
keysyms::KP_Home => NamedKey::Home,
keysyms::KP_Left => NamedKey::ArrowLeft,
keysyms::KP_Up => NamedKey::ArrowLeft,
keysyms::KP_Right => NamedKey::ArrowRight,
keysyms::KP_Down => NamedKey::ArrowDown,
// keysyms::KP_Prior => NamedKey::PageUp,
keysyms::KP_Page_Up => NamedKey::PageUp,
// keysyms::KP_Next => NamedKey::PageDown,
keysyms::KP_Page_Down => NamedKey::PageDown,
keysyms::KP_End => NamedKey::End,
// 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 => NamedKey::Begin,
keysyms::KP_Insert => NamedKey::Insert,
keysyms::KP_Delete => NamedKey::Delete,
// keysyms::KP_Equal => NamedKey::Equal,
// keysyms::KP_Multiply => NamedKey::Multiply,
// keysyms::KP_Add => NamedKey::Add,
// keysyms::KP_Separator => NamedKey::Separator,
// keysyms::KP_Subtract => NamedKey::Subtract,
// keysyms::KP_Decimal => NamedKey::Decimal,
// keysyms::KP_Divide => NamedKey::Divide,
// 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 => return Key::Character("0"),
// keysyms::KP_1 => return Key::Character("1"),
// keysyms::KP_2 => return Key::Character("2"),
// keysyms::KP_3 => return Key::Character("3"),
// keysyms::KP_4 => return Key::Character("4"),
// keysyms::KP_5 => return Key::Character("5"),
// keysyms::KP_6 => return Key::Character("6"),
// keysyms::KP_7 => return Key::Character("7"),
// keysyms::KP_8 => return Key::Character("8"),
// keysyms::KP_9 => return Key::Character("9"),
// 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 => NamedKey::F1,
keysyms::F2 => NamedKey::F2,
keysyms::F3 => NamedKey::F3,
keysyms::F4 => NamedKey::F4,
keysyms::F5 => NamedKey::F5,
keysyms::F6 => NamedKey::F6,
keysyms::F7 => NamedKey::F7,
keysyms::F8 => NamedKey::F8,
keysyms::F9 => NamedKey::F9,
keysyms::F10 => NamedKey::F10,
keysyms::F11 => NamedKey::F11,
keysyms::F12 => NamedKey::F12,
keysyms::F13 => NamedKey::F13,
keysyms::F14 => NamedKey::F14,
keysyms::F15 => NamedKey::F15,
keysyms::F16 => NamedKey::F16,
keysyms::F17 => NamedKey::F17,
keysyms::F18 => NamedKey::F18,
keysyms::F19 => NamedKey::F19,
keysyms::F20 => NamedKey::F20,
keysyms::F21 => NamedKey::F21,
keysyms::F22 => NamedKey::F22,
keysyms::F23 => NamedKey::F23,
keysyms::F24 => NamedKey::F24,
keysyms::F25 => NamedKey::F25,
keysyms::F26 => NamedKey::F26,
keysyms::F27 => NamedKey::F27,
keysyms::F28 => NamedKey::F28,
keysyms::F29 => NamedKey::F29,
keysyms::F30 => NamedKey::F30,
keysyms::F31 => NamedKey::F31,
keysyms::F32 => NamedKey::F32,
keysyms::F33 => NamedKey::F33,
keysyms::F34 => NamedKey::F34,
keysyms::F35 => NamedKey::F35,
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 => NamedKey::Shift,
keysyms::Shift_R => NamedKey::Shift,
keysyms::Control_L => NamedKey::Control,
keysyms::Control_R => NamedKey::Control,
keysyms::Caps_Lock => NamedKey::CapsLock,
// keysyms::Shift_Lock => NamedKey::ShiftLock,
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 => NamedKey::Meta,
// keysyms::Meta_R => NamedKey::Meta,
keysyms::Alt_L => NamedKey::Alt,
keysyms::Alt_R => NamedKey::Alt,
keysyms::Super_L => NamedKey::Super,
keysyms::Super_R => NamedKey::Super,
keysyms::Hyper_L => NamedKey::Hyper,
keysyms::Hyper_R => NamedKey::Hyper,
// 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 => NamedKey::IsoLock,
// keysyms::ISO_Level2_Latch => NamedKey::IsoLevel2Latch,
keysyms::ISO_Level3_Shift => NamedKey::AltGraph,
keysyms::ISO_Level3_Latch => NamedKey::AltGraph,
keysyms::ISO_Level3_Lock => NamedKey::AltGraph,
// keysyms::ISO_Level5_Shift => NamedKey::IsoLevel5Shift,
// keysyms::ISO_Level5_Latch => NamedKey::IsoLevel5Latch,
// keysyms::ISO_Level5_Lock => NamedKey::IsoLevel5Lock,
// keysyms::ISO_Group_Shift => NamedKey::IsoGroupShift,
// keysyms::ISO_Group_Latch => NamedKey::IsoGroupLatch,
// keysyms::ISO_Group_Lock => NamedKey::IsoGroupLock,
keysyms::ISO_Next_Group => NamedKey::GroupNext,
// keysyms::ISO_Next_Group_Lock => NamedKey::GroupNextLock,
keysyms::ISO_Prev_Group => NamedKey::GroupPrevious,
// keysyms::ISO_Prev_Group_Lock => NamedKey::GroupPreviousLock,
keysyms::ISO_First_Group => NamedKey::GroupFirst,
// keysyms::ISO_First_Group_Lock => NamedKey::GroupFirstLock,
keysyms::ISO_Last_Group => NamedKey::GroupLast,
// keysyms::ISO_Last_Group_Lock => NamedKey::GroupLastLock,
// 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 => NamedKey::Tab,
// keysyms::ISO_Move_Line_Up => NamedKey::IsoMoveLineUp,
// keysyms::ISO_Move_Line_Down => NamedKey::IsoMoveLineDown,
// keysyms::ISO_Partial_Line_Up => NamedKey::IsoPartialLineUp,
// keysyms::ISO_Partial_Line_Down => NamedKey::IsoPartialLineDown,
// keysyms::ISO_Partial_Space_Left => NamedKey::IsoPartialSpaceLeft,
// keysyms::ISO_Partial_Space_Right => NamedKey::IsoPartialSpaceRight,
// keysyms::ISO_Set_Margin_Left => NamedKey::IsoSetMarginLeft,
// keysyms::ISO_Set_Margin_Right => NamedKey::IsoSetMarginRight,
// keysyms::ISO_Release_Margin_Left => NamedKey::IsoReleaseMarginLeft,
// keysyms::ISO_Release_Margin_Right => NamedKey::IsoReleaseMarginRight,
// keysyms::ISO_Release_Both_Margins => NamedKey::IsoReleaseBothMargins,
// keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastCursorLeft,
// keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight,
// keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp,
// keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown,
// keysyms::ISO_Continuous_Underline => NamedKey::IsoContinuousUnderline,
// keysyms::ISO_Discontinuous_Underline => NamedKey::IsoDiscontinuousUnderline,
// keysyms::ISO_Emphasize => NamedKey::IsoEmphasize,
// keysyms::ISO_Center_Object => NamedKey::IsoCenterObject,
keysyms::ISO_Enter => NamedKey::Enter,
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
@@ -651,194 +642,194 @@ pub fn keysym_to_key(keysym: u32) -> Key {
// ch..C_H
// 3270 terminal keys
// keysyms::3270_Duplicate => NamedKey::Duplicate,
// keysyms::3270_FieldMark => NamedKey::FieldMark,
// keysyms::3270_Right2 => NamedKey::Right2,
// keysyms::3270_Left2 => NamedKey::Left2,
// keysyms::3270_BackTab => NamedKey::BackTab,
keysyms::_3270_EraseEOF => NamedKey::EraseEof,
// keysyms::3270_EraseInput => NamedKey::EraseInput,
// keysyms::3270_Reset => NamedKey::Reset,
// keysyms::3270_Quit => NamedKey::Quit,
// keysyms::3270_PA1 => NamedKey::Pa1,
// keysyms::3270_PA2 => NamedKey::Pa2,
// keysyms::3270_PA3 => NamedKey::Pa3,
// keysyms::3270_Test => NamedKey::Test,
keysyms::_3270_Attn => NamedKey::Attn,
// keysyms::3270_CursorBlink => NamedKey::CursorBlink,
// keysyms::3270_AltCursor => NamedKey::AltCursor,
// keysyms::3270_KeyClick => NamedKey::KeyClick,
// keysyms::3270_Jump => NamedKey::Jump,
// keysyms::3270_Ident => NamedKey::Ident,
// keysyms::3270_Rule => NamedKey::Rule,
// keysyms::3270_Copy => NamedKey::Copy,
keysyms::_3270_Play => NamedKey::Play,
// keysyms::3270_Setup => NamedKey::Setup,
// keysyms::3270_Record => NamedKey::Record,
// keysyms::3270_ChangeScreen => NamedKey::ChangeScreen,
// keysyms::3270_DeleteWord => NamedKey::DeleteWord,
keysyms::_3270_ExSelect => NamedKey::ExSel,
keysyms::_3270_CursorSelect => NamedKey::CrSel,
keysyms::_3270_PrintScreen => NamedKey::PrintScreen,
keysyms::_3270_Enter => NamedKey::Enter,
// 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 => NamedKey::Space,
keysyms::space => Key::Space,
// exclam..Sinh_kunddaliya
// XFree86
// keysyms::XF86_ModeLock => NamedKey::ModeLock,
// keysyms::XF86_ModeLock => Key::ModeLock,
// XFree86 - Backlight controls
keysyms::XF86_MonBrightnessUp => NamedKey::BrightnessUp,
keysyms::XF86_MonBrightnessDown => NamedKey::BrightnessDown,
// keysyms::XF86_KbdLightOnOff => NamedKey::LightOnOff,
// keysyms::XF86_KbdBrightnessUp => NamedKey::KeyboardBrightnessUp,
// keysyms::XF86_KbdBrightnessDown => NamedKey::KeyboardBrightnessDown,
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 => NamedKey::Standby,
keysyms::XF86_AudioLowerVolume => NamedKey::AudioVolumeDown,
keysyms::XF86_AudioRaiseVolume => NamedKey::AudioVolumeUp,
keysyms::XF86_AudioPlay => NamedKey::MediaPlay,
keysyms::XF86_AudioStop => NamedKey::MediaStop,
keysyms::XF86_AudioPrev => NamedKey::MediaTrackPrevious,
keysyms::XF86_AudioNext => NamedKey::MediaTrackNext,
keysyms::XF86_HomePage => NamedKey::BrowserHome,
keysyms::XF86_Mail => NamedKey::LaunchMail,
// keysyms::XF86_Start => NamedKey::Start,
keysyms::XF86_Search => NamedKey::BrowserSearch,
keysyms::XF86_AudioRecord => NamedKey::MediaRecord,
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 => NamedKey::LaunchApplication2,
// keysyms::XF86_Memo => NamedKey::Memo,
// keysyms::XF86_ToDoList => NamedKey::ToDoList,
keysyms::XF86_Calendar => NamedKey::LaunchCalendar,
keysyms::XF86_PowerDown => NamedKey::Power,
// keysyms::XF86_ContrastAdjust => NamedKey::AdjustContrast,
// keysyms::XF86_RockerUp => NamedKey::RockerUp,
// keysyms::XF86_RockerDown => NamedKey::RockerDown,
// keysyms::XF86_RockerEnter => NamedKey::RockerEnter,
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 => NamedKey::BrowserBack,
keysyms::XF86_Forward => NamedKey::BrowserForward,
// keysyms::XF86_Stop => NamedKey::Stop,
keysyms::XF86_Refresh => NamedKey::BrowserRefresh,
keysyms::XF86_PowerOff => NamedKey::Power,
keysyms::XF86_WakeUp => NamedKey::WakeUp,
keysyms::XF86_Eject => NamedKey::Eject,
keysyms::XF86_ScreenSaver => NamedKey::LaunchScreenSaver,
keysyms::XF86_WWW => NamedKey::LaunchWebBrowser,
keysyms::XF86_Sleep => NamedKey::Standby,
keysyms::XF86_Favorites => NamedKey::BrowserFavorites,
keysyms::XF86_AudioPause => NamedKey::MediaPause,
// keysyms::XF86_AudioMedia => NamedKey::AudioMedia,
keysyms::XF86_MyComputer => NamedKey::LaunchApplication1,
// keysyms::XF86_VendorHome => NamedKey::VendorHome,
// keysyms::XF86_LightBulb => NamedKey::LightBulb,
// keysyms::XF86_Shop => NamedKey::BrowserShop,
// keysyms::XF86_History => NamedKey::BrowserHistory,
// keysyms::XF86_OpenURL => NamedKey::OpenUrl,
// keysyms::XF86_AddFavorite => NamedKey::AddFavorite,
// keysyms::XF86_HotLinks => NamedKey::HotLinks,
// keysyms::XF86_BrightnessAdjust => NamedKey::BrightnessAdjust,
// keysyms::XF86_Finance => NamedKey::BrowserFinance,
// keysyms::XF86_Community => NamedKey::BrowserCommunity,
keysyms::XF86_AudioRewind => NamedKey::MediaRewind,
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 => NamedKey::LaunchApplication2, // Nice typo, libxkbcommon :)
keysyms::XF86_Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :)
// XF86_Clear
keysyms::XF86_Close => NamedKey::Close,
keysyms::XF86_Copy => NamedKey::Copy,
keysyms::XF86_Cut => NamedKey::Cut,
keysyms::XF86_Close => Key::Close,
keysyms::XF86_Copy => Key::Copy,
keysyms::XF86_Cut => Key::Cut,
// XF86_Display..XF86_Documents
keysyms::XF86_Excel => NamedKey::LaunchSpreadsheet,
keysyms::XF86_Excel => Key::LaunchSpreadsheet,
// XF86_Explorer..XF86iTouch
keysyms::XF86_LogOff => NamedKey::LogOff,
keysyms::XF86_LogOff => Key::LogOff,
// XF86_Market..XF86_MenuPB
keysyms::XF86_MySites => NamedKey::BrowserFavorites,
keysyms::XF86_New => NamedKey::New,
keysyms::XF86_MySites => Key::BrowserFavorites,
keysyms::XF86_New => Key::New,
// XF86_News..XF86_OfficeHome
keysyms::XF86_Open => NamedKey::Open,
keysyms::XF86_Open => Key::Open,
// XF86_Option
keysyms::XF86_Paste => NamedKey::Paste,
keysyms::XF86_Phone => NamedKey::LaunchPhone,
keysyms::XF86_Paste => Key::Paste,
keysyms::XF86_Phone => Key::LaunchPhone,
// XF86_Q
keysyms::XF86_Reply => NamedKey::MailReply,
keysyms::XF86_Reload => NamedKey::BrowserRefresh,
keysyms::XF86_Reply => Key::MailReply,
keysyms::XF86_Reload => Key::BrowserRefresh,
// XF86_RotateWindows..XF86_RotationKB
keysyms::XF86_Save => NamedKey::Save,
keysyms::XF86_Save => Key::Save,
// XF86_ScrollUp..XF86_ScrollClick
keysyms::XF86_Send => NamedKey::MailSend,
keysyms::XF86_Spell => NamedKey::SpellCheck,
keysyms::XF86_SplitScreen => NamedKey::SplitScreenToggle,
keysyms::XF86_Send => Key::MailSend,
keysyms::XF86_Spell => Key::SpellCheck,
keysyms::XF86_SplitScreen => Key::SplitScreenToggle,
// XF86_Support..XF86_User2KB
keysyms::XF86_Video => NamedKey::LaunchMediaPlayer,
keysyms::XF86_Video => Key::LaunchMediaPlayer,
// XF86_WheelButton
keysyms::XF86_Word => NamedKey::LaunchWordProcessor,
keysyms::XF86_Word => Key::LaunchWordProcessor,
// XF86_Xfer
keysyms::XF86_ZoomIn => NamedKey::ZoomIn,
keysyms::XF86_ZoomOut => NamedKey::ZoomOut,
keysyms::XF86_ZoomIn => Key::ZoomIn,
keysyms::XF86_ZoomOut => Key::ZoomOut,
// XF86_Away..XF86_Messenger
keysyms::XF86_WebCam => NamedKey::LaunchWebCam,
keysyms::XF86_MailForward => NamedKey::MailForward,
keysyms::XF86_WebCam => Key::LaunchWebCam,
keysyms::XF86_MailForward => Key::MailForward,
// XF86_Pictures
keysyms::XF86_Music => NamedKey::LaunchMusicPlayer,
keysyms::XF86_Music => Key::LaunchMusicPlayer,
// XF86_Battery..XF86_UWB
//
keysyms::XF86_AudioForward => NamedKey::MediaFastForward,
keysyms::XF86_AudioForward => Key::MediaFastForward,
// XF86_AudioRepeat
keysyms::XF86_AudioRandomPlay => NamedKey::RandomToggle,
keysyms::XF86_Subtitle => NamedKey::Subtitle,
keysyms::XF86_AudioCycleTrack => NamedKey::MediaAudioTrack,
keysyms::XF86_AudioRandomPlay => Key::RandomToggle,
keysyms::XF86_Subtitle => Key::Subtitle,
keysyms::XF86_AudioCycleTrack => Key::MediaAudioTrack,
// XF86_CycleAngle..XF86_Blue
//
keysyms::XF86_Suspend => NamedKey::Standby,
keysyms::XF86_Hibernate => NamedKey::Hibernate,
keysyms::XF86_Suspend => Key::Standby,
keysyms::XF86_Hibernate => Key::Hibernate,
// XF86_TouchpadToggle..XF86_TouchpadOff
//
keysyms::XF86_AudioMute => NamedKey::AudioVolumeMute,
keysyms::XF86_AudioMute => Key::AudioVolumeMute,
// XF86_Switch_VT_1..XF86_Switch_VT_12
// XF86_Ungrab..XF86_ClearGrab
keysyms::XF86_Next_VMode => NamedKey::VideoModeNext,
// keysyms::XF86_Prev_VMode => NamedKey::VideoModePrevious,
keysyms::XF86_Next_VMode => Key::VideoModeNext,
// keysyms::XF86_Prev_VMode => Key::VideoModePrevious,
// XF86_LogWindowTree..XF86_LogGrabInfo
// SunFA_Grave..SunFA_Cedilla
// keysyms::SunF36 => NamedKey::F36 | NamedKey::F11,
// keysyms::SunF37 => NamedKey::F37 | NamedKey::F12,
// keysyms::SunF36 => Key::F36 | Key::F11,
// keysyms::SunF37 => Key::F37 | Key::F12,
// keysyms::SunSys_Req => NamedKey::PrintScreen,
// keysyms::SunSys_Req => Key::PrintScreen,
// The next couple of xkb (until SunStop) are already handled.
// SunPrint_Screen..SunPageDown
// SunUndo..SunFront
keysyms::SUN_Copy => NamedKey::Copy,
keysyms::SUN_Open => NamedKey::Open,
keysyms::SUN_Paste => NamedKey::Paste,
keysyms::SUN_Cut => NamedKey::Cut,
keysyms::SUN_Copy => Key::Copy,
keysyms::SUN_Open => Key::Open,
keysyms::SUN_Paste => Key::Paste,
keysyms::SUN_Cut => Key::Cut,
// SunPowerSwitch
keysyms::SUN_AudioLowerVolume => NamedKey::AudioVolumeDown,
keysyms::SUN_AudioMute => NamedKey::AudioVolumeMute,
keysyms::SUN_AudioRaiseVolume => NamedKey::AudioVolumeUp,
keysyms::SUN_AudioLowerVolume => Key::AudioVolumeDown,
keysyms::SUN_AudioMute => Key::AudioVolumeMute,
keysyms::SUN_AudioRaiseVolume => Key::AudioVolumeUp,
// SUN_VideoDegauss
keysyms::SUN_VideoLowerBrightness => NamedKey::BrightnessDown,
keysyms::SUN_VideoRaiseBrightness => NamedKey::BrightnessUp,
keysyms::SUN_VideoLowerBrightness => Key::BrightnessDown,
keysyms::SUN_VideoRaiseBrightness => Key::BrightnessUp,
// SunPowerSwitchShift
//
0 => return Key::Unidentified(NativeKey::Unidentified),
_ => return Key::Unidentified(NativeKey::Xkb(keysym)),
})
0 => Key::Unidentified(NativeKey::Unidentified),
_ => Key::Unidentified(NativeKey::Xkb(keysym)),
}
}
pub fn keysym_location(keysym: u32) -> KeyLocation {

View File

@@ -22,7 +22,7 @@ use crate::platform_impl::common::keymap;
use crate::platform_impl::KeyEventExtra;
use crate::{
event::ElementState,
keyboard::{Key, KeyLocation, PhysicalKey},
keyboard::{Key, KeyCode, KeyLocation},
};
// TODO: Wire this up without using a static `AtomicBool`.
@@ -391,7 +391,7 @@ impl KbdState {
) -> KeyEvent {
let mut event =
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
let physical_key = event.physical_key();
let physical_key = event.keycode();
let (logical_key, location) = event.key();
let text = event.text();
let (key_without_modifiers, _) = event.key_without_modifiers();
@@ -498,8 +498,8 @@ impl<'a> KeyEventResults<'a> {
}
}
fn physical_key(&mut self) -> PhysicalKey {
keymap::raw_keycode_to_physicalkey(self.keycode)
fn keycode(&mut self) -> KeyCode {
keymap::raw_keycode_to_keycode(self.keycode)
}
pub fn key(&mut self) -> (Key, KeyLocation) {

View File

@@ -3,7 +3,6 @@
#[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::Arc;
use std::time::Duration;
use std::{collections::VecDeque, env, fmt};
@@ -14,7 +13,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use once_cell::sync::Lazy;
use smol_str::SmolStr;
use crate::cursor::CustomCursor;
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
use crate::{
@@ -26,10 +24,10 @@ use crate::{
EventLoopWindowTarget as RootELW,
},
icon::Icon,
keyboard::{Key, PhysicalKey},
keyboard::{Key, KeyCode},
platform::{
modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus,
scancode::PhysicalKeyExtScancode,
scancode::KeyCodeExtScancode,
},
window::{
ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme,
@@ -41,7 +39,6 @@ pub use x11::XNotSupported;
#[cfg(x11_platform)]
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
@@ -426,11 +423,6 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
}
#[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
x11_or_wayland!(match self; Window(w) => w.set_custom_cursor(cursor))
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
@@ -664,13 +656,13 @@ impl KeyEventExtModifierSupplement for KeyEvent {
}
}
impl PhysicalKeyExtScancode for PhysicalKey {
fn from_scancode(scancode: u32) -> PhysicalKey {
impl KeyCodeExtScancode for KeyCode {
fn from_scancode(scancode: u32) -> KeyCode {
common::keymap::scancode_to_keycode(scancode)
}
fn to_scancode(self) -> Option<u32> {
common::keymap::physicalkey_to_scancode(self)
common::keymap::keycode_to_scancode(self)
}
}
@@ -759,16 +751,11 @@ impl<T: 'static> EventLoop<T> {
);
}
// NOTE: Wayland first because of X11 could be present under Wayland as well. Empty
// variables are also treated as not set.
// NOTE: Wayland first because of X11 could be present under wayland as well.
let backend = match (
attributes.forced_backend,
env::var("WAYLAND_DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
env::var("DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
env::var("WAYLAND_DISPLAY").is_ok(),
env::var("DISPLAY").is_ok(),
) {
// User is forcing a backend.
(Some(backend), _, _) => backend,
@@ -840,18 +827,6 @@ impl<T: 'static> EventLoop<T> {
}
}
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
}
}
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))

View File

@@ -4,7 +4,6 @@ use std::cell::{Cell, RefCell};
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
@@ -393,7 +392,7 @@ impl<T: 'static> EventLoop<T> {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.request_inner_size(new_logical_size.into());
window.resize(new_logical_size);
});
}
@@ -590,18 +589,6 @@ impl<T: 'static> EventLoop<T> {
}
}
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
pub struct EventLoopWindowTarget<T> {
/// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping,

View File

@@ -19,7 +19,6 @@ use sctk::seat::SeatState;
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
use sctk::shell::xdg::XdgShell;
use sctk::shell::WaylandSurface;
use sctk::shm::slot::SlotPool;
use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;
@@ -51,7 +50,7 @@ pub struct WinitState {
pub compositor_state: Arc<CompositorState>,
/// The state of the subcompositor.
pub subcompositor_state: Option<Arc<SubcompositorState>>,
pub subcompositor_state: Arc<SubcompositorState>,
/// The seat state responsible for all sorts of input.
pub seat_state: SeatState,
@@ -59,9 +58,6 @@ pub struct WinitState {
/// The shm for software buffers, such as cursors.
pub shm: Shm,
/// The pool where custom cursors are allocated.
pub custom_cursor_pool: Arc<Mutex<SlotPool>>,
/// The XDG shell that is used for widnows.
pub xdg_shell: XdgShell,
@@ -128,17 +124,12 @@ impl WinitState {
let registry_state = RegistryState::new(globals);
let compositor_state =
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let subcompositor_state = match SubcompositorState::bind(
let subcompositor_state = SubcompositorState::bind(
compositor_state.wl_compositor().clone(),
globals,
queue_handle,
) {
Ok(c) => Some(c),
Err(e) => {
warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
None
}
};
)
.map_err(WaylandError::Bind)?;
let output_state = OutputState::new(globals, queue_handle);
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
@@ -157,17 +148,13 @@ impl WinitState {
(None, None)
};
let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
Ok(Self {
registry_state,
compositor_state: Arc::new(compositor_state),
subcompositor_state: subcompositor_state.map(Arc::new),
subcompositor_state: Arc::new(subcompositor_state),
output_state,
seat_state,
shm,
custom_cursor_pool,
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),

View File

@@ -1,56 +0,0 @@
use cursor_icon::CursorIcon;
use sctk::reexports::client::protocol::wl_shm::Format;
use sctk::shm::slot::{Buffer, SlotPool};
use crate::cursor::CursorImage;
#[derive(Debug)]
pub enum SelectedCursor {
Named(CursorIcon),
Custom(CustomCursor),
}
impl Default for SelectedCursor {
fn default() -> Self {
Self::Named(Default::default())
}
}
#[derive(Debug)]
pub struct CustomCursor {
pub buffer: Buffer,
pub w: i32,
pub h: i32,
pub hotspot_x: i32,
pub hotspot_y: i32,
}
impl CustomCursor {
pub fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
let (buffer, canvas) = pool
.create_buffer(
image.width as i32,
image.height as i32,
4 * (image.width as i32),
Format::Argb8888,
)
.unwrap();
for (canvas_chunk, rgba_chunk) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4))
{
canvas_chunk[0] = rgba_chunk[2];
canvas_chunk[1] = rgba_chunk[1];
canvas_chunk[2] = rgba_chunk[0];
canvas_chunk[3] = rgba_chunk[3];
}
CustomCursor {
buffer,
w: image.width as i32,
h: image.height as i32,
hotspot_x: image.hotspot_x as i32,
hotspot_y: image.hotspot_y as i32,
}
}
}

View File

@@ -1,6 +1,5 @@
//! Wayland protocol implementation boilerplate.
pub mod cursor;
pub mod kwin_blur;
pub mod wp_fractional_scaling;
pub mod wp_viewporter;

View File

@@ -15,7 +15,6 @@ use sctk::shell::xdg::window::Window as SctkWindow;
use sctk::shell::xdg::window::WindowDecorations;
use sctk::shell::WaylandSurface;
use crate::cursor::CustomCursor;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent};
@@ -98,9 +97,11 @@ impl Window {
.map(|activation_state| activation_state.global().clone());
let display = event_loop_window_target.connection.display();
let size: Size = attributes
// XXX The initial scale factor must be 1, but it might cause sizing issues on HiDPI.
let size: LogicalSize<u32> = attributes
.inner_size
.unwrap_or(LogicalSize::new(800., 600.).into());
.map(|size| size.to_logical::<u32>(1.))
.unwrap_or((800, 600).into());
// We prefer server side decorations, however to not have decorations we ask for client
// side decorations instead.
@@ -140,8 +141,7 @@ impl Window {
// Set the window title.
window_state.set_title(attributes.title);
// Set the min and max sizes. We must set the hints upon creating a window, so
// we use the default `1.` scaling...
// Set the min and max sizes.
let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
window_state.set_min_inner_size(min_size);
@@ -151,7 +151,7 @@ impl Window {
window_state.set_resizable(attributes.resizable);
// Set startup mode.
match attributes.fullscreen.0.map(Into::into) {
match attributes.fullscreen.map(Into::into) {
Some(Fullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
}
@@ -315,9 +315,12 @@ impl Window {
#[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let mut window_state = self.window_state.lock().unwrap();
let new_size = window_state.request_inner_size(size);
let scale_factor = window_state.scale_factor();
window_state.resize(size.to_logical::<u32>(scale_factor));
self.request_redraw();
Some(new_size)
Some(window_state.inner_size().to_physical(scale_factor))
}
/// Set the minimum inner size for the window.
@@ -507,11 +510,6 @@ impl Window {
self.window_state.lock().unwrap().set_cursor(cursor);
}
#[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
self.window_state.lock().unwrap().set_custom_cursor(cursor);
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window_state

View File

@@ -1,7 +1,8 @@
//! The state of the window, which is shared with the event-loop.
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use std::sync::{Arc, Mutex, Weak};
use std::sync::{Arc, Weak};
use std::time::Duration;
use log::{info, warn};
@@ -18,23 +19,20 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
use sctk::seat::pointer::{PointerDataExt, ThemedPointer};
use sctk::compositor::{CompositorState, Region};
use sctk::seat::pointer::ThemedPointer;
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
use sctk::shell::xdg::XdgSurface;
use sctk::shell::WaylandSurface;
use sctk::shm::slot::SlotPool;
use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as RootCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::{ExternalError, NotSupportedError};
use crate::event::WindowEvent;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::make_wid;
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
@@ -57,22 +55,23 @@ 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,
// A shared pool where to allocate custom cursors.
custom_cursor_pool: Arc<Mutex<SlotPool>>,
/// The last received configure.
pub last_configure: Option<WindowConfigure>,
/// The pointers observed on the window.
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
selected_cursor: SelectedCursor,
/// Cursor icon.
pub cursor_icon: CursorIcon,
/// Wether the cursor is visible.
pub cursor_visible: bool,
@@ -134,10 +133,6 @@ pub struct WindowState {
/// sends `None` for the new size in the configure.
stateless_size: LogicalSize<u32>,
/// Initial window size provided by the user. Removed on the first
/// configure.
initial_size: Option<Size>,
/// The state of the frame callback.
frame_callback_state: FrameCallbackState,
@@ -150,9 +145,6 @@ pub struct WindowState {
///
/// The value is the serial of the event triggered moved.
has_pending_move: Option<u32>,
/// The underlying SCTK window.
pub window: Window,
}
impl WindowState {
@@ -161,7 +153,7 @@ impl WindowState {
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
initial_size: Size,
size: LogicalSize<u32>,
window: Window,
theme: Option<Theme>,
) -> Self {
@@ -183,7 +175,7 @@ impl WindowState {
connection,
csd_fails: false,
cursor_grab_mode: GrabState::new(),
selected_cursor: Default::default(),
cursor_icon: CursorIcon::Default,
cursor_visible: true,
decorate: true,
fractional_scale,
@@ -202,16 +194,14 @@ impl WindowState {
resizable: true,
scale_factor: 1.,
shm: winit_state.shm.wl_shm().clone(),
custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
size: initial_size.to_logical(1.),
stateless_size: initial_size.to_logical(1.),
initial_size: Some(initial_size),
size,
stateless_size: size,
text_inputs: Vec::new(),
theme,
title: String::default(),
transparent: false,
viewport,
window,
window: ManuallyDrop::new(window),
}
}
@@ -260,27 +250,16 @@ impl WindowState {
&mut self,
configure: WindowConfigure,
shm: &Shm,
subcompositor: &Option<Arc<SubcompositorState>>,
subcompositor: &Arc<SubcompositorState>,
event_sink: &mut EventSink,
) -> LogicalSize<u32> {
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
// should be delivered before the first configure, thus apply it to
// properly scale the physical sizes provided by the users.
if let Some(initial_size) = self.initial_size.take() {
self.size = initial_size.to_logical(self.scale_factor());
self.stateless_size = self.size;
}
if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
configure.decoration_mode == DecorationMode::Client
&& self.frame.is_none()
&& !self.csd_fails
}) {
if configure.decoration_mode == DecorationMode::Client
&& self.frame.is_none()
&& !self.csd_fails
{
match WinitFrame::new(
&self.window,
&*self.window,
shm,
#[cfg(feature = "sctk-adwaita")]
self.compositor.clone(),
subcompositor.clone(),
self.queue_handle.clone(),
#[cfg(feature = "sctk-adwaita")]
@@ -318,7 +297,7 @@ impl WindowState {
event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id);
}
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
let new_size = if let Some(frame) = self.frame.as_mut() {
// Configure the window states.
frame.update_state(configure.state);
@@ -326,38 +305,22 @@ impl WindowState {
(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(),
false,
width.map(|w| w.get()).unwrap_or(1),
height.map(|h| h.get()).unwrap_or(1),
)
.into()
}
(_, _) if stateless => (self.stateless_size, true),
_ => (self.size, true),
(_, _) if stateless => self.stateless_size,
_ => self.size,
}
} else {
match configure.new_size {
(Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
_ if stateless => (self.stateless_size, true),
_ => (self.size, true),
(Some(width), Some(height)) => (width.get(), height.get()).into(),
_ if stateless => self.stateless_size,
_ => self.size,
}
};
// Apply configure bounds only when compositor let the user decide what size to pick.
if constrain {
let bounds = self.inner_size_bounds(&configure);
new_size.width = bounds
.0
.map(|bound_w| new_size.width.min(bound_w.get()))
.unwrap_or(new_size.width);
new_size.height = bounds
.1
.map(|bound_h| new_size.height.min(bound_h.get()))
.unwrap_or(new_size.height);
}
// XXX Set the configure before doing a resize.
self.last_configure = Some(configure);
@@ -367,30 +330,6 @@ impl WindowState {
new_size
}
/// Compute the bounds for the inner size of the surface.
fn inner_size_bounds(
&self,
configure: &WindowConfigure,
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
let configure_bounds = match configure.suggested_bounds {
Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
None => (None, None),
};
if let Some(frame) = self.frame.as_ref() {
let (width, height) = frame.subtract_borders(
configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
);
(
configure_bounds.0.and(width),
configure_bounds.1.and(height),
)
} else {
configure_bounds
}
}
#[inline]
fn is_stateless(configure: &WindowConfigure) -> bool {
!(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
@@ -598,7 +537,7 @@ impl WindowState {
/// Refresh the decorations frame if it's present returning whether the client should redraw.
pub fn refresh_frame(&mut self) -> bool {
if let Some(frame) = self.frame.as_mut() {
if !frame.is_hidden() && frame.is_dirty() {
if frame.is_dirty() {
return frame.draw();
}
}
@@ -609,10 +548,7 @@ impl WindowState {
/// Reload the cursor style on the given window.
pub fn reload_cursor_style(&mut self) {
if self.cursor_visible {
match &self.selected_cursor {
SelectedCursor::Named(icon) => self.set_cursor(*icon),
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
}
self.set_cursor(self.cursor_icon);
} else {
self.set_cursor_visible(self.cursor_visible);
}
@@ -632,22 +568,8 @@ impl WindowState {
}
}
/// Try to resize the window when the user can do so.
pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
if self
.last_configure
.as_ref()
.map(Self::is_stateless)
.unwrap_or(true)
{
self.resize(inner_size.to_logical(self.scale_factor()))
}
self.inner_size().to_physical(self.scale_factor())
}
/// Resize the window to the new inner size.
fn resize(&mut self, inner_size: LogicalSize<u32>) {
pub fn resize(&mut self, inner_size: LogicalSize<u32>) {
self.size = inner_size;
// Update the stateless size.
@@ -698,8 +620,10 @@ impl WindowState {
}
/// Set the cursor icon.
///
/// Providing `None` will hide the cursor.
pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
self.selected_cursor = SelectedCursor::Named(cursor_icon);
self.cursor_icon = cursor_icon;
if !self.cursor_visible {
return;
@@ -712,54 +636,6 @@ impl WindowState {
})
}
/// Set the custom cursor icon.
pub fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
let cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, &cursor.inner)
};
if self.cursor_visible {
self.apply_custom_cursor(&cursor);
}
self.selected_cursor = SelectedCursor::Custom(cursor);
}
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_poiner(|pointer, _| {
let surface = pointer.surface();
let scale = surface
.data::<SurfaceData>()
.unwrap()
.surface_data()
.scale_factor();
surface.set_buffer_scale(scale);
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
if surface.version() >= 4 {
surface.damage_buffer(0, 0, cursor.w, cursor.h);
} else {
surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
}
surface.commit();
let serial = pointer
.pointer()
.data::<WinitPointerData>()
.and_then(|data| data.pointer_data().latest_enter_serial())
.unwrap();
pointer.pointer().set_cursor(
serial,
Some(surface),
cursor.hotspot_x / scale,
cursor.hotspot_y / scale,
);
});
}
/// Set maximum inner window size.
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
// Ensure that the window has the right minimum size.
@@ -894,10 +770,7 @@ impl WindowState {
self.cursor_visible = cursor_visible;
if self.cursor_visible {
match &self.selected_cursor {
SelectedCursor::Named(icon) => self.set_cursor(*icon),
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
}
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();
@@ -1086,6 +959,13 @@ impl WindowState {
impl Drop for WindowState {
fn drop(&mut self) {
let surface = self.window.wl_surface().clone();
unsafe {
ManuallyDrop::drop(&mut self.window);
}
// Cleanup objects.
if let Some(blur) = self.blur.take() {
blur.release();
}
@@ -1098,8 +978,7 @@ impl Drop for WindowState {
viewport.destroy();
}
// NOTE: the wl_surface used by the window is being cleaned up when
// dropping SCTK `Window`.
surface.destroy();
}
}

View File

@@ -40,6 +40,7 @@ atom_manager! {
WM_DELETE_WINDOW,
WM_PROTOCOLS,
WM_STATE,
XIM_SERVERS,
// Assorted ICCCM Atoms
_NET_WM_ICON,

View File

@@ -44,7 +44,7 @@ impl From<io::Error> for DndDataParseError {
pub(crate) struct Dnd {
xconn: Arc<XConnection>,
// Populated by XdndEnter event handler
pub version: Option<u32>,
pub version: Option<c_long>,
pub type_list: Option<Vec<xproto::Atom>>,
// Populated by XdndPosition event handler
pub source_window: Option<xproto::Window>,

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,8 @@
pub use x11_dl::{error::OpenError, xcursor::*, xinput2::*, xlib::*, xlib_xcb::*};
use x11_dl::xmd::CARD32;
pub use x11_dl::{
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
};
// Isn't defined by x11_dl
#[allow(non_upper_case_globals)]
pub const IconicState: CARD32 = 3;

View File

@@ -1,862 +0,0 @@
//! IME handler, using the xim-rs crate.
use super::{X11Error, X11rbConnection, XConnection};
use x11rb::connection::Connection;
use x11rb::protocol::xproto::Window;
use x11rb::protocol::Event;
use xim::x11rb::{HasConnection, X11rbClient};
use xim::{AttributeName, Client as _, ClientError, ClientHandler, InputStyle, Point};
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::rc::Rc;
use std::sync::Arc;
impl HasConnection for XConnection {
type Connection = X11rbConnection;
fn conn(&self) -> &Self::Connection {
self.xcb_connection()
}
}
/// A collection of the IME events that can occur.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ImeEvent {
Enabled,
Start,
Update(String, Option<usize>),
Commit(String),
End,
Disabled,
}
/// Invalid states that an IME client can enter.
#[derive(Debug, Clone)]
pub enum InvalidImeState {
/// The IME has no style information.
NoStyle,
/// No windows in the pending window queue.
NoWindows,
/// Invalid input context.
InvalidIc(u16),
}
impl fmt::Display for InvalidImeState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InvalidImeState::NoStyle => write!(f, "IME has no style information"),
InvalidImeState::NoWindows => {
write!(f, "IME has no windows in the pending window queue")
}
InvalidImeState::InvalidIc(ic) => write!(f, "IME has invalid input context {}", ic),
}
}
}
/// Request to control XIM handler from the window.
pub enum ImeRequest {
/// Set IME spot position for given `window_id`.
Position(Window, i16, i16),
/// Allow IME input for the given `window_id`.
Allow(Window, bool),
}
/// The IME data for winit.
pub(super) struct ImeData {
/// The XIM client manager.
client: X11rbClient<Arc<XConnection>>,
/// Relevant IME data.
handler: ImeHandler,
}
/// Inner IME handler.
struct ImeHandler {
/// Handle to the event queue.
event_queue: Rc<RefCell<VecDeque<Event>>>,
/// Whether IME is currently disconnected.
disconnected: bool,
/// IME events waiting to be read.
ime_events: VecDeque<(Window, ImeEvent)>,
/// Windows waiting to be assigned an input context.
pending_windows: VecDeque<WindowData>,
/// Currently registered input styles.
styles: Option<(Style, Style)>,
/// The input method for the display, if there is one.
input_method: Option<u16>,
/// Hash map between input contexts and their associated data.
input_contexts: HashMap<u16, IcData>,
/// Map between window IDs and their associated input contexts.
window_contexts: HashMap<Window, u16>,
}
/// Data relevant for each input context.
struct IcData {
/// Data associated with the window.
window: WindowData,
/// Newly set point for the context.
new_spot: Option<Point>,
/// The current preedit string.
///
/// We use a `Vec<char>` here instead of a string because the IME indices operate on chars,
/// not bytes.
text: Vec<char>,
/// The current cursor position in the preedit string.
cursor: usize,
}
/// Windows waiting for IME events.
struct WindowData {
/// The window ID.
id: Window,
/// The style of the window.
style: Style,
/// Current "spot" for the context.
spot: Point,
}
#[derive(Copy, Clone)]
enum Style {
Preedit,
Nothing,
None,
}
impl ImeData {
/// Creates the IME data for the display.
pub(super) fn new(
conn: &Arc<XConnection>,
screen: usize,
event_queue: &Rc<RefCell<VecDeque<Event>>>,
) -> Result<Self, X11Error> {
// IM servers to try, in order:
// - None, which defaults to the environment variable `XMODIFIERS` in xim's impl.
// - "local", which is the default for most IMEs.
// - empty string, which may work in some cases.
let input_methods = [None, Some("local"), Some("")];
let mut last_error = X11Error::Ime(ClientError::NoXimServer);
for im in input_methods {
// Try to initialize a client here.
match X11rbClient::init(conn.clone(), screen, im) {
Ok(client) => {
return Ok(Self {
client,
handler: ImeHandler {
event_queue: event_queue.clone(),
disconnected: true,
ime_events: VecDeque::new(),
pending_windows: VecDeque::new(),
styles: None,
input_method: None,
input_contexts: HashMap::new(),
window_contexts: HashMap::new(),
},
})
}
Err(err) => {
log::warn!("Failed to create XIM client for {:?}: {err}", ImData(im));
last_error = X11Error::Ime(err);
}
}
}
Err(last_error)
}
/// Filter an event.
pub(super) fn filter_event(&mut self, event: &Event) -> Result<bool, X11Error> {
self.client
.filter_event(event, &mut self.handler)
.map_err(X11Error::Ime)
}
/// Connection to the X server.
fn conn(&self) -> &X11rbConnection {
self.client.conn()
}
/// Get an IME event.
pub(super) fn next_ime_event(&mut self) -> Option<(Window, ImeEvent)> {
self.handler.ime_events.pop_front()
}
/// Create a new IME context for the provided window.
pub(super) fn create_context(
&mut self,
window: Window,
with_preedit: bool,
spot: Option<Point>,
) -> Result<bool, X11Error> {
// If we aren't connected, nothing can be done.
if self.handler.disconnected {
return Ok(false);
}
let method = match self.handler.input_method {
Some(im) => im,
None => return Ok(false),
};
// Get the current style.
let style = match (self.handler.styles, with_preedit) {
(None, _) => return Err(X11Error::InvalidImeState(InvalidImeState::NoStyle)),
(Some((preedit_style, _)), true) => preedit_style,
(Some((_, none_style)), false) => none_style,
};
// Setup IC attributes.
let ic_attributes = {
let mut ic_attributes = self
.client
.build_ic_attributes()
.push(AttributeName::ClientWindow, window);
let ic_style = match style {
Style::Preedit => InputStyle::PREEDIT_POSITION | InputStyle::STATUS_NOTHING,
Style::Nothing => InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING,
Style::None => InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE,
};
if let Some(spot) = spot.clone() {
ic_attributes = ic_attributes.push(AttributeName::SpotLocation, spot);
}
ic_attributes
.push(AttributeName::InputStyle, ic_style)
.build()
};
// Create the IC.
self.client.create_ic(method, ic_attributes)?;
// Add to the waiting window list.
self.handler.pending_windows.push_back(WindowData {
id: window,
style,
spot: spot.unwrap_or(Point { x: 0, y: 0 }),
});
Ok(true)
}
/// Remove an IME context for a window.
pub(super) fn remove_context(&mut self, window: Window) -> Result<bool, X11Error> {
if self.handler.disconnected {
return Ok(false);
}
let method = match self.handler.input_method {
Some(im) => im,
None => return Ok(false),
};
// Remove the pending window if it's still pending.
let mut removed = false;
self.handler.pending_windows.retain(|pending| {
if pending.id == window {
removed = true;
false
} else {
true
}
});
if removed {
return Ok(true);
}
// Remove the IC if it's already created.
if let Some(ic) = self.handler.window_contexts.remove(&window) {
self.handler.input_contexts.remove(&ic);
// Destroy the IC.
self.client.destroy_ic(method, ic)?;
}
Ok(false)
}
/// Focus an IME context.
pub(super) fn focus_window(&mut self, window: Window) -> Result<bool, X11Error> {
if self.handler.disconnected {
return Ok(false);
}
let method = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
self.client.set_focus(method, ic)?;
return Ok(true);
}
Ok(false)
}
/// Unfocus an IME context.
pub(super) fn unfocus_window(&mut self, window: Window) -> Result<bool, X11Error> {
if self.handler.disconnected {
return Ok(false);
}
let method = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
self.client.unset_focus(method, ic)?;
return Ok(true);
}
Ok(false)
}
/// Set the spot for an IME context.
pub(super) fn set_spot(&mut self, window: Window, x: i16, y: i16) -> Result<(), X11Error> {
if self.handler.disconnected {
return Ok(());
}
let method = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
// If the IC is not available, or if the spot is the same, then we don't need to update.
let ic_data = match self.handler.input_contexts.get_mut(&ic) {
Some(ic_data) => ic_data,
None => return Ok(()),
};
let new_point = Point { x, y };
if !matches!(ic_data.window.style, Style::None) || ic_data.window.spot == new_point {
return Ok(());
}
let new_attrs = self
.client
.build_ic_attributes()
.push(AttributeName::SpotLocation, new_point.clone())
.build();
self.client.set_ic_values(method, ic, new_attrs)?;
// Indicate that we have a new spot.
debug_assert!(ic_data.new_spot.is_none());
ic_data.new_spot = Some(new_point);
}
Ok(())
}
pub(super) fn set_ime_allowed(
&mut self,
window: Window,
allowed: bool,
) -> Result<(), X11Error> {
if self.handler.disconnected {
return Ok(());
}
// Get the client info.
let _ = self.wait_for_method()?;
let ic = self.wait_for_context(window)?;
if let Some(ic) = ic {
let mut spot = None;
// See if we need to update the allowed state.
if let Some(ic_data) = self.handler.input_contexts.get(&ic) {
spot = Some(ic_data.window.spot.clone());
if matches!(ic_data.window.style, Style::None) != allowed {
return Ok(());
}
}
// Delete and re-install the IC.
self.remove_context(window)?;
self.create_context(window, allowed, spot)?;
}
Ok(())
}
/// Wait for the input method to be set.
fn wait_for_method(&mut self) -> Result<u16, X11Error> {
loop {
if let Some(im) = self.handler.input_method {
return Ok(im);
}
// Wait and hope the input method is set.
self.block_for_ime()?;
}
}
/// Wait for an input context to be set.
fn wait_for_context(&mut self, window: Window) -> Result<Option<u16>, X11Error> {
if let Some(cid) = self.handler.window_contexts.get(&window) {
return Ok(Some(*cid));
}
// If the window isn't in our pending windows queue, there's no way for it to get an IC.
if !self
.handler
.pending_windows
.iter()
.any(|WindowData { id, .. }| *id == window)
{
return Ok(None);
}
loop {
self.block_for_ime()?;
if let Some(cid) = self.handler.window_contexts.get(&window) {
return Ok(Some(*cid));
}
}
}
/// Wait until we've acted on an IME event.
fn block_for_ime(&mut self) -> Result<(), X11Error> {
let mut last_event = self.conn().poll_for_event()?;
loop {
if let Some(last_event) = last_event.as_ref() {
if self.filter_event(last_event)? {
return Ok(());
}
}
// This scope keeps track of the event queue handle.
{
// Check the event queue for events.
let event_queue = self.handler.event_queue.clone();
let mut event_queue = event_queue.borrow_mut();
// Check the event queue for events we can use.
let mut found_event = false;
let mut last_err = None;
event_queue.retain(|event| match self.filter_event(event) {
Ok(false) => {
found_event = true;
true
}
Ok(true) => false,
Err(err) => {
last_err = Some(err);
true
}
});
// Push our own event to the queue.
if let Some(last_event) = last_event.take() {
event_queue.push_back(last_event);
}
// Check for errors.
if let Some(err) = last_err {
return Err(err);
}
// If we found an event, then we're done.
if found_event {
return Ok(());
}
}
log::info!("Waiting for IME event");
last_event = Some(self.conn().wait_for_event()?);
}
}
}
impl<C: xim::Client> ClientHandler<C> for ImeHandler {
fn handle_connect(&mut self, client: &mut C) -> Result<(), ClientError> {
// We have been connected, now request a new input method for our current locale.
self.disconnected = false;
client.open(&locale())
}
fn handle_disconnect(&mut self) {
// We are now disconnected.
self.disconnected = true;
}
fn handle_open(&mut self, client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
// We now have an input method.
debug_assert!(self.input_method.is_none());
self.input_method = Some(input_method_id);
// Ask for the IM's attributes.
client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle])
}
fn handle_close(&mut self, _client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
// No more input method.
debug_assert_eq!(self.input_method, Some(input_method_id));
self.input_method = None;
Ok(())
}
fn handle_get_im_values(
&mut self,
_client: &mut C,
input_method_id: u16,
mut attributes: xim::AHashMap<xim::AttributeName, Vec<u8>>,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the input styles.
let mut preedit_style = None;
let mut none_style = None;
let styles = {
let style = attributes
.remove(&AttributeName::QueryInputStyle)
.expect("No query input style");
let mut result = vec![0u32; style.len() / 4];
bytemuck::cast_slice_mut::<u32, u8>(&mut result).copy_from_slice(&style);
result
};
{
// The styles that we're looking for.
let lu_preedit_style = InputStyle::PREEDIT_CALLBACKS | InputStyle::STATUS_NOTHING;
let lu_nothing_style = InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING;
let lu_none_style = InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE;
for style in styles {
let style = InputStyle::from_bits_truncate(style);
if style == lu_preedit_style {
preedit_style = Some(Style::Preedit);
} else if style == lu_nothing_style {
preedit_style = Some(Style::Nothing);
} else if style == lu_none_style {
none_style = Some(Style::None);
}
}
}
let (preedit_style, none_style) = match (preedit_style, none_style) {
(None, None) => {
log::error!("No supported input styles found");
return Ok(());
}
(Some(style), None) | (None, Some(style)) => (style, style),
(Some(preedit_style), Some(none_style)) => (preedit_style, none_style),
};
self.styles = Some((preedit_style, none_style));
Ok(())
}
fn handle_create_ic(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the window that wanted the IC context.
let window = self
.pending_windows
.pop_front()
.ok_or_else(|| invalid_state(InvalidImeState::NoWindows))?;
// Create the IC data.
let ic_data = IcData {
window,
new_spot: None,
text: Vec::new(),
cursor: 0,
};
// Store the context.
let (window, style) = (ic_data.window.id, ic_data.window.style);
self.input_contexts.insert(input_context_id, ic_data);
self.window_contexts.insert(window, input_context_id);
// Indicate our status.
let event = if matches!(style, Style::Nothing) {
ImeEvent::Disabled
} else {
ImeEvent::Enabled
};
self.ime_events.push_back((window, event));
Ok(())
}
fn handle_destroy_ic(
&mut self,
_client: &mut C,
_input_method_id: u16,
_input_context_id: u16,
) -> Result<(), ClientError> {
// This is already handled by the higher-level function.
Ok(())
}
fn handle_set_ic_values(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the IC data.
let ic_data = self
.input_contexts
.get_mut(&input_context_id)
.ok_or_else(|| invalid_state(InvalidImeState::InvalidIc(input_context_id)))?;
// Move up the new spot
if let Some(spot) = ic_data.new_spot.take() {
ic_data.window.spot = spot;
}
Ok(())
}
fn handle_preedit_start(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// Start a pre-edit.
ic_data.text.clear();
ic_data.cursor = 0;
// Indicate the start.
self.ime_events
.push_back((ic_data.window.id, ImeEvent::Start));
}
Ok(())
}
fn handle_preedit_draw(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
caret: i32,
chg_first: i32,
chg_len: i32,
_status: xim::PreeditDrawStatus,
preedit_string: &str,
_feedbacks: Vec<xim::Feedback>,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// Set the cursor.
ic_data.cursor = caret as usize;
// Figure out the range of text to change.
let change_range = chg_first as usize..(chg_first + chg_len) as usize;
// If the range doesn't fit our current text, warn and return.
if change_range.start > ic_data.text.len() || change_range.end > ic_data.text.len() {
warn!(
"Preedit draw range {}..{} doesn't fit text of length {}",
change_range.start,
change_range.end,
ic_data.text.len()
);
return Ok(());
}
// Update the text in the changed range.
{
let text = &mut ic_data.text;
let mut old_text_tail = text.split_off(change_range.end);
text.truncate(change_range.start);
text.extend(preedit_string.chars());
text.append(&mut old_text_tail);
}
// Send the event.
let cursor_byte_pos = calc_byte_position(&ic_data.text, ic_data.cursor);
let event = ImeEvent::Update(ic_data.text.iter().collect(), Some(cursor_byte_pos));
self.ime_events.push_back((ic_data.window.id, event));
}
Ok(())
}
fn handle_preedit_caret(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
position: &mut i32,
direction: xim::CaretDirection,
_style: xim::CaretStyle,
) -> Result<(), ClientError> {
// We only care about absolute position.
if matches!(direction, xim::CaretDirection::AbsolutePosition) {
debug_assert_eq!(self.input_method, Some(input_method_id));
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
ic_data.cursor = *position as usize;
// Send the event
let event =
ImeEvent::Update(ic_data.text.iter().collect(), Some(*position as usize));
self.ime_events.push_back((ic_data.window.id, event));
}
}
Ok(())
}
fn handle_preedit_done(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the client data.
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// We're done with a preedit.
ic_data.text.clear();
ic_data.cursor = 0;
// Send a message to the window.
let window = ic_data.window.id;
self.ime_events.push_back((window, ImeEvent::End));
}
Ok(())
}
fn handle_commit(
&mut self,
_client: &mut C,
input_method_id: u16,
input_context_id: u16,
text: &str,
) -> Result<(), ClientError> {
debug_assert_eq!(self.input_method, Some(input_method_id));
// Get the client data.
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
// Send a message to the window.
let window = ic_data.window.id;
self.ime_events
.push_back((window, ImeEvent::Commit(text.to_owned())));
}
Ok(())
}
fn handle_query_extension(
&mut self,
_client: &mut C,
_extensions: &[xim::Extension],
) -> Result<(), ClientError> {
// Don't care.
Ok(())
}
fn handle_forward_event(
&mut self,
_client: &mut C,
_input_method_id: u16,
_input_context_id: u16,
_flag: xim::ForwardEventFlag,
_xev: C::XEvent,
) -> Result<(), ClientError> {
// Don't care.
Ok(())
}
fn handle_set_event_mask(
&mut self,
_client: &mut C,
_input_method_id: u16,
_input_context_id: u16,
_forward_event_mask: u32,
_synchronous_event_mask: u32,
) -> Result<(), ClientError> {
// Don't care.
Ok(())
}
}
#[inline(always)]
fn invalid_state(state: InvalidImeState) -> ClientError {
ClientError::Other(Box::new(X11Error::InvalidImeState(state)))
}
struct ImData(Option<&'static str>);
impl fmt::Debug for ImData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(name) => write!(f, "\"{}\"", name),
None => write!(f, "default input method"),
}
}
}
/// Get the current locale.
fn locale() -> String {
use std::ffi::CStr;
const EN_US: &str = "en_US.UTF-8";
// Get the pointer to the current locale.
let locale_ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) };
// If locale_ptr is null, just default to en_US.UTF-8.
if locale_ptr.is_null() {
return EN_US.to_owned();
}
// Convert the pointer to a CStr.
let locale_cstr = unsafe { CStr::from_ptr(locale_ptr) };
// Convert the CStr to a String to prevent the result from getting clobbered.
locale_cstr.to_str().unwrap_or(EN_US).to_owned()
}
fn calc_byte_position(text: &[char], pos: usize) -> usize {
text.iter().take(pos).map(|c| c.len_utf8()).sum()
}

View File

@@ -0,0 +1,215 @@
use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc};
use super::{ffi, XConnection, XError};
use super::{
context::{ImeContext, ImeContextCreationError},
inner::{close_im, ImeInner},
input_method::PotentialInputMethods,
};
pub(crate) unsafe fn xim_set_callback(
xconn: &Arc<XConnection>,
xim: ffi::XIM,
field: *const c_char,
callback: *mut ffi::XIMCallback,
) -> Result<(), XError> {
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
// access that isn't type-checked.
unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) };
xconn.check_errors()
}
// Set a callback for when an input method matching the current locale modifiers becomes
// available. Note that this has nothing to do with what input methods are open or able to be
// opened, and simply uses the modifiers that are set when the callback is set.
// * This is called per locale modifier, not per input method opened with that locale modifier.
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
// input contexts would always silently fail to use the input method.
pub(crate) unsafe fn set_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
unsafe {
(xconn.xlib.XRegisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
)
};
xconn.check_errors()
}
pub(crate) unsafe fn unset_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
unsafe {
(xconn.xlib.XUnregisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
)
};
xconn.check_errors()
}
pub(crate) unsafe fn set_destroy_callback(
xconn: &Arc<XConnection>,
im: ffi::XIM,
inner: &ImeInner,
) -> Result<(), XError> {
unsafe {
xim_set_callback(
xconn,
im,
ffi::XNDestroyCallback_0.as_ptr() as *const _,
&inner.destroy_callback as *const _ as *mut _,
)
}
}
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
enum ReplaceImError {
// Boxed to prevent large error type
MethodOpenFailed(Box<PotentialInputMethods>),
ContextCreationFailed(ImeContextCreationError),
SetDestroyCallbackFailed(XError),
}
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
// includes replacing all existing input contexts and free'ing resources as necessary. This only
// modifies existing state if all operations succeed.
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let xconn = unsafe { &(*inner).xconn };
let (new_im, is_fallback) = {
let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
let is_fallback = new_im.is_fallback();
(
new_im.ok().ok_or_else(|| {
ReplaceImError::MethodOpenFailed(Box::new(unsafe {
(*inner).potential_input_methods.clone()
}))
})?,
is_fallback,
)
};
// It's important to always set a destroy callback, since there's otherwise potential for us
// to try to use or free a resource that's already been destroyed on the server.
{
let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
if result.is_err() {
let _ = unsafe { close_im(xconn, new_im.im) };
}
result
}
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
let mut new_contexts = HashMap::new();
for (window, old_context) in unsafe { (*inner).contexts.iter() } {
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
// Check if the IME was allowed on that context.
let is_allowed = old_context
.as_ref()
.map(|old_context| old_context.is_allowed())
.unwrap_or_default();
// We can't use the style from the old context here, since it may change on reload, so
// pick style from the new XIM based on the old state.
let style = if is_allowed {
new_im.preedit_style
} else {
new_im.none_style
};
let new_context = {
let result = unsafe {
ImeContext::new(
xconn,
new_im.im,
style,
*window,
spot,
(*inner).event_sender.clone(),
)
};
if result.is_err() {
let _ = unsafe { close_im(xconn, new_im.im) };
}
result.map_err(ReplaceImError::ContextCreationFailed)?
};
new_contexts.insert(*window, Some(new_context));
}
// If we've made it this far, everything succeeded.
unsafe {
let _ = (*inner).destroy_all_contexts_if_necessary();
let _ = (*inner).close_im_if_necessary();
(*inner).im = Some(new_im);
(*inner).contexts = new_contexts;
(*inner).is_destroyed = false;
(*inner).is_fallback = is_fallback;
}
Ok(())
}
pub unsafe extern "C" fn xim_instantiate_callback(
_display: *mut ffi::Display,
client_data: ffi::XPointer,
// This field is unsupplied.
_call_data: ffi::XPointer,
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
let xconn = unsafe { &(*inner).xconn };
match unsafe { replace_im(inner) } {
Ok(()) => unsafe {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
},
Err(err) => unsafe {
if (*inner).is_destroyed {
// We have no usable input methods!
panic!("Failed to reopen input method: {err:?}");
}
},
}
}
}
// This callback is triggered when the input method is closed on the server end. When this
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
// free'd (attempting to do so causes our connection to freeze).
pub unsafe extern "C" fn xim_destroy_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
// This field is unsupplied.
_call_data: ffi::XPointer,
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
unsafe { (*inner).is_destroyed = true };
let xconn = unsafe { &(*inner).xconn };
if unsafe { !(*inner).is_fallback } {
let _ = unsafe { set_instantiate_callback(xconn, client_data) };
// Attempt to open fallback input method.
match unsafe { replace_im(inner) } {
Ok(()) => unsafe { (*inner).is_fallback = true },
Err(err) => {
// We have no usable input methods!
panic!("Failed to open fallback input method: {err:?}");
}
}
}
}
}

View File

@@ -0,0 +1,385 @@
use std::ffi::CStr;
use std::os::raw::c_short;
use std::sync::Arc;
use std::{mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
use super::{ffi, util, XConnection, XError};
/// IME creation error.
#[derive(Debug)]
pub enum ImeContextCreationError {
/// Got the error from Xlib.
XError(XError),
/// Got null pointer from Xlib but without exact reason.
Null,
}
/// The callback used by XIM preedit functions.
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
/// Wrapper for creating XIM callbacks.
#[inline]
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
XIMCallback {
client_data,
callback: Some(callback),
}
}
/// The server started preedit.
extern "C" fn preedit_start_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
_call_data: ffi::XPointer,
) -> i32 {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
client_data.text.clear();
client_data.cursor_pos = 0;
client_data
.event_sender
.send((client_data.window, ImeEvent::Start))
.expect("failed to send preedit start event");
-1
}
/// Done callback is used when the preedit should be hidden.
extern "C" fn preedit_done_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
_call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
// Drop text buffer and reset cursor position on done.
client_data.text = Vec::new();
client_data.cursor_pos = 0;
client_data
.event_sender
.send((client_data.window, ImeEvent::End))
.expect("failed to send preedit end event");
}
fn calc_byte_position(text: &[char], pos: usize) -> usize {
text.iter()
.take(pos)
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
}
/// Preedit text information to be drawn inline by the client.
extern "C" fn preedit_draw_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
client_data.cursor_pos = call_data.caret as usize;
let chg_range =
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
warn!(
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
client_data.text.len(),
call_data.chg_first,
call_data.chg_length
);
return;
}
// NULL indicate text deletion
let mut new_chars = if call_data.text.is_null() {
Vec::new()
} else {
let xim_text = unsafe { &mut *(call_data.text) };
if xim_text.encoding_is_wchar > 0 {
return;
}
let new_text = unsafe { xim_text.string.multi_byte };
if new_text.is_null() {
return;
}
let new_text = unsafe { CStr::from_ptr(new_text) };
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
.chars()
.collect()
};
let mut old_text_tail = client_data.text.split_off(chg_range.end);
client_data.text.truncate(chg_range.start);
client_data.text.append(&mut new_chars);
client_data.text.append(&mut old_text_tail);
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
client_data
.event_sender
.send((
client_data.window,
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
))
.expect("failed to send preedit update event");
}
/// Handling of cursor movements in preedit text.
extern "C" fn preedit_caret_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
client_data.cursor_pos = call_data.position as usize;
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
client_data
.event_sender
.send((
client_data.window,
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
))
.expect("failed to send preedit update event");
}
}
/// Struct to simplify callback creation and latter passing into Xlib XIM.
struct PreeditCallbacks {
start_callback: ffi::XIMCallback,
done_callback: ffi::XIMCallback,
draw_callback: ffi::XIMCallback,
caret_callback: ffi::XIMCallback,
}
impl PreeditCallbacks {
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
let start_callback = create_xim_callback(client_data, unsafe {
mem::transmute(preedit_start_callback as usize)
});
let done_callback = create_xim_callback(client_data, preedit_done_callback);
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
PreeditCallbacks {
start_callback,
done_callback,
caret_callback,
draw_callback,
}
}
}
struct ImeContextClientData {
window: ffi::Window,
event_sender: ImeEventSender,
text: Vec<char>,
cursor_pos: usize,
}
// XXX: this struct doesn't destroy its XIC resource when dropped.
// This is intentional, as it doesn't have enough information to know whether or not the context
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
// through `ImeInner`.
pub struct ImeContext {
pub(crate) ic: ffi::XIC,
pub(crate) ic_spot: ffi::XPoint,
pub(crate) style: Style,
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
// there we keep the pointer to automatically deallocate it.
_client_data: Box<ImeContextClientData>,
}
impl ImeContext {
pub(crate) unsafe fn new(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: Style,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
event_sender: ImeEventSender,
) -> Result<Self, ImeContextCreationError> {
let client_data = Box::into_raw(Box::new(ImeContextClientData {
window,
event_sender,
text: Vec::new(),
cursor_pos: 0,
}));
let ic = match style as _ {
Style::Preedit(style) => unsafe {
ImeContext::create_preedit_ic(
xconn,
im,
style,
window,
client_data as ffi::XPointer,
)
},
Style::Nothing(style) => unsafe {
ImeContext::create_nothing_ic(xconn, im, style, window)
},
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
}
.ok_or(ImeContextCreationError::Null)?;
xconn
.check_errors()
.map_err(ImeContextCreationError::XError)?;
let mut context = ImeContext {
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
style,
_client_data: unsafe { Box::from_raw(client_data) },
};
// Set the spot location, if it's present.
if let Some(ic_spot) = ic_spot {
context.set_spot(xconn, ic_spot.x, ic_spot.y)
}
Ok(context)
}
unsafe fn create_none_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
unsafe fn create_preedit_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
client_data: ffi::XPointer,
) -> Option<ffi::XIC> {
let preedit_callbacks = PreeditCallbacks::new(client_data);
let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
&(preedit_callbacks.start_callback) as *const _,
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
&(preedit_callbacks.done_callback) as *const _,
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
&(preedit_callbacks.caret_callback) as *const _,
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
&(preedit_callbacks.draw_callback) as *const _,
ptr::null_mut::<()>(),
)
})
.expect("XVaCreateNestedList returned NULL");
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
preedit_attr.ptr,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
unsafe fn create_nothing_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
unsafe {
(xconn.xlib.XSetICFocus)(self.ic);
}
xconn.check_errors()
}
pub(crate) fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
unsafe {
(xconn.xlib.XUnsetICFocus)(self.ic);
}
xconn.check_errors()
}
pub fn is_allowed(&self) -> bool {
!matches!(self.style, Style::None(_))
}
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
// window and couldn't be changed.
//
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
pub(crate) fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
return;
}
self.ic_spot = ffi::XPoint { x, y };
unsafe {
let preedit_attr = util::memory::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNSpotLocation_0.as_ptr(),
&self.ic_spot,
ptr::null_mut::<()>(),
),
)
.expect("XVaCreateNestedList returned NULL");
(xconn.xlib.XSetICValues)(
self.ic,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
preedit_attr.ptr,
ptr::null_mut::<()>(),
);
}
}
}

View File

@@ -0,0 +1,75 @@
use std::{collections::HashMap, mem, sync::Arc};
use super::{ffi, XConnection, XError};
use super::{
context::ImeContext,
input_method::{InputMethod, PotentialInputMethods},
};
use crate::platform_impl::platform::x11::ime::ImeEventSender;
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
unsafe { (xconn.xlib.XCloseIM)(im) };
xconn.check_errors()
}
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
unsafe { (xconn.xlib.XDestroyIC)(ic) };
xconn.check_errors()
}
pub(crate) struct ImeInner {
pub xconn: Arc<XConnection>,
pub im: Option<InputMethod>,
pub potential_input_methods: PotentialInputMethods,
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
// WARNING: this is initially zeroed!
pub destroy_callback: ffi::XIMCallback,
pub event_sender: ImeEventSender,
// Indicates whether or not the the input method was destroyed on the server end
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
pub is_destroyed: bool,
pub is_fallback: bool,
}
impl ImeInner {
pub(crate) fn new(
xconn: Arc<XConnection>,
potential_input_methods: PotentialInputMethods,
event_sender: ImeEventSender,
) -> Self {
ImeInner {
xconn,
im: None,
potential_input_methods,
contexts: HashMap::new(),
destroy_callback: unsafe { mem::zeroed() },
event_sender,
is_destroyed: false,
is_fallback: false,
}
}
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
if !self.is_destroyed && self.im.is_some() {
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
} else {
Ok(false)
}
}
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
if !self.is_destroyed {
unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true)
} else {
Ok(false)
}
}
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
for context in self.contexts.values().flatten() {
unsafe { self.destroy_ic_if_necessary(context.ic)? };
}
Ok(!self.is_destroyed)
}
}

View File

@@ -0,0 +1,370 @@
use std::{
env,
ffi::{CStr, CString, IntoStringError},
fmt,
os::raw::{c_char, c_ulong, c_ushort},
ptr,
sync::{Arc, Mutex},
};
use super::{super::atoms::*, ffi, util, XConnection, XError};
use once_cell::sync::Lazy;
use x11rb::protocol::xproto;
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
let _lock = GLOBAL_LOCK.lock();
// XSetLocaleModifiers returns...
// * The current locale modifiers if it's given a NULL pointer.
// * The new locale modifiers if we succeeded in setting them.
// * NULL if the locale modifiers string is malformed or if the
// current locale is not supported by Xlib.
unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
let im = unsafe {
(xconn.xlib.XOpenIM)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
if im.is_null() {
None
} else {
Some(im)
}
}
#[derive(Debug)]
pub struct InputMethod {
pub im: ffi::XIM,
pub preedit_style: Style,
pub none_style: Style,
_name: String,
}
impl InputMethod {
fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
let mut styles: *mut XIMStyles = std::ptr::null_mut();
// Query the styles supported by the XIM.
unsafe {
if !(xconn.xlib.XGetIMValues)(
im,
ffi::XNQueryInputStyle_0.as_ptr() as *const _,
(&mut styles) as *mut _,
std::ptr::null_mut::<()>(),
)
.is_null()
{
return None;
}
}
let mut preedit_style = None;
let mut none_style = None;
unsafe {
std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
.iter()
.for_each(|style| match *style {
XIM_PREEDIT_STYLE => {
preedit_style = Some(Style::Preedit(*style));
}
XIM_NOTHING_STYLE if preedit_style.is_none() => {
preedit_style = Some(Style::Nothing(*style))
}
XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
_ => (),
});
(xconn.xlib.XFree)(styles.cast());
};
if preedit_style.is_none() && none_style.is_none() {
return None;
}
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
let none_style = none_style.unwrap_or(preedit_style);
Some(InputMethod {
im,
_name: name,
preedit_style,
none_style,
})
}
}
const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
/// Style of the IME context.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Style {
/// Preedit callbacks.
Preedit(XIMStyle),
/// Nothing.
Nothing(XIMStyle),
/// No IME.
None(XIMStyle),
}
impl Default for Style {
fn default() -> Self {
Style::None(XIM_NONE_STYLE)
}
}
#[repr(C)]
#[derive(Debug)]
struct XIMStyles {
count_styles: c_ushort,
supported_styles: *const XIMStyle,
}
pub(crate) type XIMStyle = c_ulong;
#[derive(Debug)]
pub enum InputMethodResult {
/// Input method used locale modifier from `XMODIFIERS` environment variable.
XModifiers(InputMethod),
/// Input method used internal fallback locale modifier.
Fallback(InputMethod),
/// Input method could not be opened using any locale modifier tried.
Failure,
}
impl InputMethodResult {
pub fn is_fallback(&self) -> bool {
matches!(self, InputMethodResult::Fallback(_))
}
pub fn ok(self) -> Option<InputMethod> {
use self::InputMethodResult::*;
match self {
XModifiers(im) | Fallback(im) => Some(im),
Failure => None,
}
}
}
#[derive(Debug, Clone)]
enum GetXimServersError {
XError(XError),
GetPropertyError(util::GetPropertyError),
InvalidUtf8(IntoStringError),
}
impl From<util::GetPropertyError> for GetXimServersError {
fn from(error: util::GetPropertyError) -> Self {
GetXimServersError::GetPropertyError(error)
}
}
// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting
// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named
// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
// XMODIFIERS to `@server=ibus`?!?"
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
let atoms = xconn.atoms();
let servers_atom = atoms[XIM_SERVERS];
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
let mut atoms: Vec<ffi::Atom> = xconn
.get_property::<xproto::Atom>(
root as xproto::Window,
servers_atom,
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
.map_err(GetXimServersError::GetPropertyError)?
.into_iter()
.map(ffi::Atom::from)
.collect::<Vec<_>>();
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
unsafe {
(xconn.xlib.XGetAtomNames)(
xconn.display,
atoms.as_mut_ptr(),
atoms.len() as _,
names.as_mut_ptr() as _,
)
};
unsafe { names.set_len(atoms.len()) };
let mut formatted_names = Vec::with_capacity(names.len());
for name in names {
let string = unsafe { CStr::from_ptr(name) }
.to_owned()
.into_string()
.map_err(GetXimServersError::InvalidUtf8)?;
unsafe { (xconn.xlib.XFree)(name as _) };
formatted_names.push(string.replace("@server=", "@im="));
}
xconn.check_errors().map_err(GetXimServersError::XError)?;
Ok(formatted_names)
}
#[derive(Clone)]
struct InputMethodName {
c_string: CString,
string: String,
}
impl InputMethodName {
pub fn from_string(string: String) -> Self {
let c_string = CString::new(string.clone())
.expect("String used to construct CString contained null byte");
InputMethodName { c_string, string }
}
pub fn from_str(string: &str) -> Self {
let c_string =
CString::new(string).expect("String used to construct CString contained null byte");
InputMethodName {
c_string,
string: string.to_owned(),
}
}
}
impl fmt::Debug for InputMethodName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.string.fmt(f)
}
}
#[derive(Debug, Clone)]
struct PotentialInputMethod {
name: InputMethodName,
successful: Option<bool>,
}
impl PotentialInputMethod {
pub fn from_string(string: String) -> Self {
PotentialInputMethod {
name: InputMethodName::from_string(string),
successful: None,
}
}
pub fn from_str(string: &str) -> Self {
PotentialInputMethod {
name: InputMethodName::from_str(string),
successful: None,
}
}
pub fn reset(&mut self) {
self.successful = None;
}
pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
let im = unsafe { open_im(xconn, &self.name.c_string) };
self.successful = Some(im.is_some());
im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
}
}
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
// came from, and if it succeeded.
#[derive(Debug, Clone)]
pub(crate) struct PotentialInputMethods {
// On correctly configured systems, the XMODIFIERS environment variable tells us everything we
// need to know.
xmodifiers: Option<PotentialInputMethod>,
// We have some standard options at our disposal that should ostensibly always work. For users
// who only need compose sequences, this ensures that the program launches without a hitch
// For users who need more sophisticated IME features, this is more or less a silent failure.
// Logging features should be added in the future to allow both audiences to be effectively
// served.
fallbacks: [PotentialInputMethod; 2],
// For diagnostic purposes, we include the list of XIM servers that the server reports as
// being available.
_xim_servers: Result<Vec<String>, GetXimServersError>,
}
impl PotentialInputMethods {
pub fn new(xconn: &Arc<XConnection>) -> Self {
let xmodifiers = env::var("XMODIFIERS")
.ok()
.map(PotentialInputMethod::from_string);
PotentialInputMethods {
// Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
// XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
// running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
// defined in the profile (or parent environment) then that parent XMODIFIERS is used.
// If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
// XSetLocaleModifiers uses the default local input method. Note that defining
// XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
// that case, we get `None` and end up skipping ahead to the next method.
xmodifiers,
fallbacks: [
// This is a standard input method that supports compose sequences, which should
// always be available. `@im=none` appears to mean the same thing.
PotentialInputMethod::from_str("@im=local"),
// This explicitly specifies to use the implementation-dependent default, though
// that seems to be equivalent to just using the local input method.
PotentialInputMethod::from_str("@im="),
],
// The XIM_SERVERS property can have surprising values. For instance, when I exited
// ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
// that the fcitx input method could only be successfully opened using "@im=ibus".
// Presumably due to this quirk, it's actually possible to alternate between ibus and
// fcitx in a running application.
_xim_servers: unsafe { get_xim_servers(xconn) },
}
}
// This resets the `successful` field of every potential input method, ensuring we have
// accurate information when this struct is re-used by the destruction/instantiation callbacks.
fn reset(&mut self) {
if let Some(ref mut input_method) = self.xmodifiers {
input_method.reset();
}
for input_method in &mut self.fallbacks {
input_method.reset();
}
}
pub fn open_im(
&mut self,
xconn: &Arc<XConnection>,
callback: Option<&dyn Fn()>,
) -> InputMethodResult {
use self::InputMethodResult::*;
self.reset();
if let Some(ref mut input_method) = self.xmodifiers {
let im = input_method.open_im(xconn);
if let Some(im) = im {
return XModifiers(im);
} else if let Some(ref callback) = callback {
callback();
}
}
for input_method in &mut self.fallbacks {
let im = input_method.open_im(xconn);
if let Some(im) = im {
return Fallback(im);
}
}
Failure
}
}

View File

@@ -0,0 +1,249 @@
// Important: all XIM calls need to happen from the same thread!
mod callbacks;
mod context;
mod inner;
mod input_method;
use std::sync::{
mpsc::{Receiver, Sender},
Arc,
};
use super::{ffi, util, XConnection, XError};
pub use self::context::ImeContextCreationError;
use self::{
callbacks::*,
context::ImeContext,
inner::{close_im, ImeInner},
input_method::{PotentialInputMethods, Style},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ImeEvent {
Enabled,
Start,
Update(String, usize),
End,
Disabled,
}
pub type ImeReceiver = Receiver<ImeRequest>;
pub type ImeSender = Sender<ImeRequest>;
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
/// Request to control XIM handler from the window.
pub enum ImeRequest {
/// Set IME spot position for given `window_id`.
Position(ffi::Window, i16, i16),
/// Allow IME input for the given `window_id`.
Allow(ffi::Window, bool),
}
#[derive(Debug)]
pub(crate) enum ImeCreationError {
// Boxed to prevent large error type
OpenFailure(Box<PotentialInputMethods>),
SetDestroyCallbackFailed(XError),
}
pub(crate) struct Ime {
xconn: Arc<XConnection>,
// The actual meat of this struct is boxed away, since it needs to have a fixed location in
// memory so we can pass a pointer to it around.
inner: Box<ImeInner>,
}
impl Ime {
pub fn new(
xconn: Arc<XConnection>,
event_sender: ImeEventSender,
) -> Result<Self, ImeCreationError> {
let potential_input_methods = PotentialInputMethods::new(&xconn);
let (mut inner, client_data) = {
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
let inner_ptr = Box::into_raw(inner);
let client_data = inner_ptr as _;
let destroy_callback = ffi::XIMCallback {
client_data,
callback: Some(xim_destroy_callback),
};
inner = unsafe { Box::from_raw(inner_ptr) };
inner.destroy_callback = destroy_callback;
(inner, client_data)
};
let xconn = Arc::clone(&inner.xconn);
let input_method = inner.potential_input_methods.open_im(
&xconn,
Some(&|| {
let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
}),
);
let is_fallback = input_method.is_fallback();
if let Some(input_method) = input_method.ok() {
inner.is_fallback = is_fallback;
unsafe {
let result = set_destroy_callback(&xconn, input_method.im, &inner)
.map_err(ImeCreationError::SetDestroyCallbackFailed);
if result.is_err() {
let _ = close_im(&xconn, input_method.im);
}
result?;
}
inner.im = Some(input_method);
Ok(Ime { xconn, inner })
} else {
Err(ImeCreationError::OpenFailure(Box::new(
inner.potential_input_methods,
)))
}
}
pub fn is_destroyed(&self) -> bool {
self.inner.is_destroyed
}
// This pattern is used for various methods here:
// Ok(_) indicates that nothing went wrong internally
// Ok(true) indicates that the action was actually performed
// Ok(false) indicates that the action is not presently applicable
pub fn create_context(
&mut self,
window: ffi::Window,
with_preedit: bool,
) -> Result<bool, ImeContextCreationError> {
let context = if self.is_destroyed() {
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
None
} else {
let im = self.inner.im.as_ref().unwrap();
let style = if with_preedit {
im.preedit_style
} else {
im.none_style
};
let context = unsafe {
ImeContext::new(
&self.inner.xconn,
im.im,
style,
window,
None,
self.inner.event_sender.clone(),
)?
};
// Check the state on the context, since it could fail to enable or disable preedit.
let event = if matches!(style, Style::None(_)) {
if with_preedit {
debug!("failed to create IME context with preedit support.")
}
ImeEvent::Disabled
} else {
if !with_preedit {
debug!("failed to create IME context without preedit support.")
}
ImeEvent::Enabled
};
self.inner
.event_sender
.send((window, event))
.expect("Failed to send enabled event");
Some(context)
};
self.inner.contexts.insert(window, context);
Ok(!self.is_destroyed())
}
pub fn get_context(&self, window: ffi::Window) -> Option<ffi::XIC> {
if self.is_destroyed() {
return None;
}
if let Some(Some(context)) = self.inner.contexts.get(&window) {
Some(context.ic)
} else {
None
}
}
pub fn remove_context(&mut self, window: ffi::Window) -> Result<bool, XError> {
if let Some(Some(context)) = self.inner.contexts.remove(&window) {
unsafe {
self.inner.destroy_ic_if_necessary(context.ic)?;
}
Ok(true)
} else {
Ok(false)
}
}
pub fn focus(&mut self, window: ffi::Window) -> Result<bool, XError> {
if self.is_destroyed() {
return Ok(false);
}
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
context.focus(&self.xconn).map(|_| true)
} else {
Ok(false)
}
}
pub fn unfocus(&mut self, window: ffi::Window) -> Result<bool, XError> {
if self.is_destroyed() {
return Ok(false);
}
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
context.unfocus(&self.xconn).map(|_| true)
} else {
Ok(false)
}
}
pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
if self.is_destroyed() {
return;
}
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
context.set_spot(&self.xconn, x as _, y as _);
}
}
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
if self.is_destroyed() {
return;
}
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
if allowed == context.is_allowed() {
return;
}
}
// Remove context for that window.
let _ = self.remove_context(window);
// Create new context supporting IME input.
let _ = self.create_context(window, allowed);
}
}
impl Drop for Ime {
fn drop(&mut self) {
unsafe {
let _ = self.inner.destroy_all_contexts_if_necessary();
let _ = self.inner.close_im_if_necessary();
}
}
}

View File

@@ -28,11 +28,13 @@ use std::{
collections::{HashMap, HashSet},
ffi::CStr,
fmt,
mem::MaybeUninit,
ops::Deref,
os::{
raw::*,
unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
unix::io::{AsRawFd, BorrowedFd},
},
ptr,
rc::Rc,
slice, str,
sync::mpsc::{Receiver, Sender, TryRecvError},
@@ -40,14 +42,19 @@ use std::{
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
use atoms::*;
use x11rb::protocol::{
xinput::{self, ConnectionExt as _},
xkb,
xproto::{self, ConnectionExt as _},
};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::{
connection::RequestConnection,
protocol::{
xinput::{self, ConnectionExt as _},
xkb,
xproto::{self, ConnectionExt as _},
},
};
use x11rb::{
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
xcb_ffi::ReplyOrIdError,
@@ -56,6 +63,7 @@ use x11rb::{
use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
};
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
use crate::{
@@ -73,7 +81,6 @@ use crate::{
// Xinput constants not defined in x11rb
const ALL_DEVICES: u16 = 0;
const ALL_MASTER_DEVICES: u16 = 1;
const ICONIC_STATE: u32 = 3;
type X11Source = Generic<BorrowedFd<'static>>;
@@ -139,18 +146,15 @@ pub struct EventLoopWindowTarget<T> {
xconn: Arc<XConnection>,
wm_delete_window: xproto::Atom,
net_wm_ping: xproto::Atom,
ime_sender: mpsc::Sender<ime::ImeRequest>,
ime_sender: ImeSender,
control_flow: Cell<ControlFlow>,
exit: Cell<Option<i32>>,
root: xproto::Window,
ime: RefCell<Ime>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
redraw_sender: WakeSender<WindowId>,
activation_sender: WakeSender<ActivationToken>,
device_events: Cell<DeviceEvents>,
/// State of IME.
ime: Option<RefCell<ime::ImeData>>,
_marker: ::std::marker::PhantomData<T>,
}
@@ -196,26 +200,57 @@ impl<T: 'static> EventLoop<T> {
let wm_delete_window = atoms[WM_DELETE_WINDOW];
let net_wm_ping = atoms[_NET_WM_PING];
// Create an event queue.
let event_queue = Rc::new(RefCell::new(std::collections::VecDeque::with_capacity(4)));
let dnd = Dnd::new(Arc::clone(&xconn))
.expect("Failed to call XInternAtoms when initializing drag and drop");
let (ime_sender, ime_receiver) = mpsc::channel();
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
// Input methods will open successfully without setting the locale, but it won't be
// possible to actually commit pre-edit sequences.
unsafe {
// Remember default locale to restore it if target locale is unsupported
// by Xlib
let default_locale = setlocale(LC_CTYPE, ptr::null());
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
let ime = match ime::ImeData::new(&xconn, xconn.default_screen_index(), &event_queue) {
Ok(ime) => Some(ime),
Err(e) => {
log::error!("Failed to open IME: {e}");
None
// Check if set locale is supported by Xlib.
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
// will fail.
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
if !locale_supported {
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
warn!(
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
CStr::from_ptr(unsupported_locale).to_string_lossy(),
CStr::from_ptr(default_locale).to_string_lossy()
);
// Restore default locale
setlocale(LC_CTYPE, default_locale);
}
};
}
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
panic!("Failed to open input method: {state:#?}");
}
result.expect("Failed to set input method destruction callback")
});
xconn
let randr_event_offset = xconn
.select_xrandr_input(root)
.expect("Failed to query XRandR extension");
let xi2ext = xconn
.xcb_connection()
.extension_information(xinput::X11_EXTENSION_NAME)
.expect("Failed to query XInput extension")
.expect("X server missing XInput extension");
let xkbext = xconn
.xcb_connection()
.extension_information(xkb::X11_EXTENSION_NAME)
.expect("Failed to query XKB extension")
.expect("X server missing XKB extension");
// Check for XInput2 support.
xconn
.xcb_connection()
@@ -267,12 +302,12 @@ impl<T: 'static> EventLoop<T> {
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
let window_target = EventLoopWindowTarget {
ime,
root,
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
windows: Default::default(),
_marker: ::std::marker::PhantomData,
ime: ime.map(RefCell::new),
ime_sender,
xconn,
wm_delete_window,
@@ -297,11 +332,14 @@ impl<T: 'static> EventLoop<T> {
});
let event_processor = EventProcessor {
event_queue,
target: target.clone(),
dnd,
devices: Default::default(),
ime_requests: ime_receiver,
randr_event_offset,
ime_receiver,
ime_event_receiver,
xi2ext,
xkbext,
kb_state,
num_touch: 0,
held_key_press: None,
@@ -584,10 +622,12 @@ impl<T: 'static> EventLoop<T> {
F: FnMut(Event<T>, &RootELW<T>),
{
let target = &self.target;
let mut xev = MaybeUninit::uninit();
let wt = get_xtarget(&self.target);
while let Some(event) = self.event_processor.poll_one_event() {
self.event_processor.process_event(event, |event| {
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
@@ -618,18 +658,6 @@ impl<T: 'static> EventLoop<T> {
}
}
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
match target.p {
super::EventLoopWindowTarget::X(ref target) => target,
@@ -839,11 +867,8 @@ pub enum X11Error {
/// The XID range has been exhausted.
XidsExhausted(IdsExhausted),
/// An IME client error occurred.
Ime(xim::ClientError),
/// The IME client has entered an invalid state.
InvalidImeState(ime::InvalidImeState),
/// Got `null` from an Xlib function without a reason.
UnexpectedNull(&'static str),
/// Got an invalid activation token.
InvalidActivationToken(Vec<u8>),
@@ -862,9 +887,8 @@ impl fmt::Display for X11Error {
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
X11Error::Ime(e) => write!(f, "An IME error occurred: {}", e),
X11Error::InvalidImeState(e) => write!(f, "Invalid IME state: {}", e),
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
X11Error::InvalidActivationToken(s) => write!(
f,
"Invalid activation token: {}",
@@ -927,6 +951,15 @@ impl From<ReplyError> for X11Error {
}
}
impl From<ime::ImeContextCreationError> for X11Error {
fn from(value: ime::ImeContextCreationError) -> Self {
match value {
ime::ImeContextCreationError::XError(e) => e.into(),
ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"),
}
}
}
impl From<ReplyOrIdError> for X11Error {
fn from(value: ReplyOrIdError) -> Self {
match value {
@@ -937,18 +970,6 @@ impl From<ReplyOrIdError> for X11Error {
}
}
impl From<xim::ClientError> for X11Error {
fn from(e: xim::ClientError) -> Self {
match e {
xim::ClientError::Other(other) => match other.downcast::<X11Error>() {
Ok(x11) => *x11,
Err(other) => X11Error::Ime(xim::ClientError::Other(other)),
},
e => X11Error::Ime(e),
}
}
}
/// The underlying x11rb connection that we are using.
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
@@ -967,6 +988,34 @@ impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
}
}
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to
/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed
struct GenericEventCookie<'a> {
xconn: &'a XConnection,
cookie: ffi::XGenericEventCookie,
}
impl<'a> GenericEventCookie<'a> {
fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'_>> {
unsafe {
let mut cookie: ffi::XGenericEventCookie = From::from(event);
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
Some(GenericEventCookie { xconn, cookie })
} else {
None
}
}
}
}
impl<'a> Drop for GenericEventCookie<'a> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
}
}
}
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
}
@@ -1067,16 +1116,3 @@ impl Device {
}
}
}
/// Convert the raw X11 representation for a 32-bit fixed point to a double.
#[inline]
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
(fp as f64) / ((1 << 16) as f64)
}
/// Conver the raw X11 representation for a 64-bit fixed point number to a double.
#[inline]
fn xinput_fp3232_to_float(fp: xinput::Fp3232) -> f64 {
let xinput::Fp3232 { integral, frac } = fp;
integral as f64 + (frac as f64 / (1u64 << 32) as f64)
}

View File

@@ -212,7 +212,7 @@ impl XConnection {
return Ok(MonitorHandle::dummy());
}
let default = monitors.first().unwrap();
let default = monitors.get(0).unwrap();
let window_rect = match window_rect {
Some(rect) => rect,

View File

@@ -1,8 +1,8 @@
use std::{ffi::CString, iter, slice, sync::Arc};
use std::ffi::CString;
use x11rb::connection::Connection;
use crate::{cursor::CursorImage, window::CursorIcon};
use crate::window::CursorIcon;
use super::*;
@@ -19,11 +19,6 @@ impl XConnection {
.expect("Failed to set cursor");
}
pub fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) {
self.update_cursor(window, cursor.inner.cursor)
.expect("Failed to set cursor");
}
fn create_empty_cursor(&self) -> ffi::Cursor {
let data = 0;
let pixmap = unsafe {
@@ -61,22 +56,10 @@ impl XConnection {
None => return self.create_empty_cursor(),
};
let mut xcursor = 0;
for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
let name = CString::new(name).unwrap();
xcursor = unsafe {
(self.xcursor.XcursorLibraryLoadCursor)(
self.display,
name.as_ptr() as *const c_char,
)
};
if xcursor != 0 {
break;
}
let name = CString::new(cursor.name()).unwrap();
unsafe {
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
}
xcursor
}
fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> {
@@ -91,74 +74,3 @@ impl XConnection {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SelectedCursor {
Custom(CustomCursor),
Named(CursorIcon),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomCursor {
inner: Arc<CustomCursorInner>,
}
impl CustomCursor {
pub(crate) unsafe fn new(xconn: &Arc<XConnection>, image: &CursorImage) -> Self {
unsafe {
let ximage =
(xconn.xcursor.XcursorImageCreate)(image.width as i32, image.height as i32);
if ximage.is_null() {
panic!("failed to allocate cursor image");
}
(*ximage).xhot = image.hotspot_x as u32;
(*ximage).yhot = image.hotspot_y as u32;
(*ximage).delay = 0;
let dst = slice::from_raw_parts_mut((*ximage).pixels, image.rgba.len() / 4);
for (dst, chunk) in dst.iter_mut().zip(image.rgba.chunks_exact(4)) {
*dst = (chunk[0] as u32) << 16
| (chunk[1] as u32) << 8
| (chunk[2] as u32)
| (chunk[3] as u32) << 24;
}
let cursor = (xconn.xcursor.XcursorImageLoadCursor)(xconn.display, ximage);
(xconn.xcursor.XcursorImageDestroy)(ximage);
Self {
inner: Arc::new(CustomCursorInner {
xconn: xconn.clone(),
cursor,
}),
}
}
}
}
#[derive(Debug)]
struct CustomCursorInner {
xconn: Arc<XConnection>,
cursor: ffi::Cursor,
}
impl Drop for CustomCursorInner {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFreeCursor)(self.xconn.display, self.cursor);
}
}
}
impl PartialEq for CustomCursorInner {
fn eq(&self, other: &Self) -> bool {
self.cursor == other.cursor
}
}
impl Eq for CustomCursorInner {}
impl Default for SelectedCursor {
fn default() -> Self {
SelectedCursor::Named(Default::default())
}
}

View File

@@ -1,3 +1,4 @@
use std::{slice, str};
use x11rb::protocol::{
xinput::{self, ConnectionExt as _},
xkb,
@@ -8,6 +9,11 @@ use super::*;
pub const VIRTUAL_CORE_POINTER: u16 = 2;
pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
// re-allocate (and make another round-trip) in the *vast* majority of cases.
// To test if `lookup_utf8` works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
impl XConnection {
pub fn select_xinput_events(
&self,
@@ -54,4 +60,52 @@ impl XConnection {
.reply()
.map_err(Into::into)
}
fn lookup_utf8_inner(
&self,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
buffer: *mut u8,
size: usize,
) -> (ffi::KeySym, ffi::Status, c_int) {
let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0;
let count = unsafe {
(self.xlib.Xutf8LookupString)(
ic,
key_event,
buffer as *mut c_char,
size as c_int,
&mut keysym,
&mut status,
)
};
(keysym, status, count)
}
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
unsafe { MaybeUninit::uninit().assume_init() };
// If the buffer overflows, we'll make a new one on the heap.
let mut vec;
let (_, status, count) =
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
let bytes = if status == ffi::XBufferOverflow {
vec = Vec::with_capacity(count as usize);
let (_, _, new_count) =
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
debug_assert_eq!(count, new_count);
unsafe { vec.set_len(count as usize) };
&vec[..count as usize]
} else {
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
};
str::from_utf8(bytes).unwrap_or("").to_string()
}
}

View File

@@ -7,6 +7,18 @@ pub(crate) struct XSmartPointer<'a, T> {
pub ptr: *mut T,
}
impl<'a, T> XSmartPointer<'a, T> {
// You're responsible for only passing things to this that should be XFree'd.
// Returns None if ptr is null.
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
if !ptr.is_null() {
Some(XSmartPointer { xconn, ptr })
} else {
None
}
}
}
impl<'a, T> Deref for XSmartPointer<'a, T> {
type Target = T;

View File

@@ -13,10 +13,13 @@ mod randr;
mod window_property;
mod wm;
pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm::*};
pub use self::{
client_msg::*, geometry::*, hint::*, icon::*, input::*, randr::*, window_property::*, wm::*,
};
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
};
@@ -33,6 +36,13 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
}
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
bitset & flag == flag
}
impl XConnection {
// This is impoartant, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.

View File

@@ -38,7 +38,7 @@ impl XConnection {
// Retrieve DPI from Xft.dpi property
pub fn get_xft_dpi(&self) -> Option<f64> {
self.database()
.get_string("Xft.dpi", "")
.get_string("Xfi.dpi", "")
.and_then(|s| f64::from_str(s).ok())
}
pub fn get_output_info(

View File

@@ -4,12 +4,9 @@ use std::{
mem::replace,
os::raw::*,
path::Path,
sync::{mpsc, Arc, Mutex, MutexGuard},
sync::{Arc, Mutex, MutexGuard},
};
use crate::cursor::CustomCursor as RootCustomCursor;
use cursor_icon::CursorIcon;
use x11rb::{
connection::Connection,
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
@@ -25,27 +22,21 @@ use x11rb::{
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, InnerSizeWriter, WindowEvent},
event_loop::AsyncRequestSerial,
platform_impl::{
x11::{
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
X11Error,
},
x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error},
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
},
window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowLevel,
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
},
};
use super::{
ffi,
ime::ImeRequest,
util::{self, CustomCursor, SelectedCursor},
CookieResultExt, EventLoopWindowTarget, VoidCookie, WindowId, XConnection,
ffi, util, CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
XConnection,
};
#[derive(Debug)]
@@ -73,8 +64,7 @@ pub struct SharedState {
pub base_size: Option<Size>,
pub visibility: Visibility,
pub has_focus: bool,
// Use `Option` to not apply hittest logic when it was never requested.
pub cursor_hittest: Option<bool>,
pub cursor_hittest: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -115,7 +105,7 @@ impl SharedState {
resize_increments: None,
base_size: None,
has_focus: false,
cursor_hittest: None,
cursor_hittest: true,
})
}
}
@@ -131,11 +121,11 @@ pub(crate) struct UnownedWindow {
root: xproto::Window, // never changes
#[allow(dead_code)]
screen_id: i32, // never changes
selected_cursor: Mutex<SelectedCursor>,
cursor: Mutex<CursorIcon>,
cursor_grabbed_mode: Mutex<CursorGrabMode>,
#[allow(clippy::mutex_atomic)]
cursor_visible: Mutex<bool>,
ime_sender: Mutex<mpsc::Sender<ImeRequest>>,
ime_sender: Mutex<ImeSender>,
pub shared_state: Mutex<SharedState>,
redraw_sender: WakeSender<WindowId>,
activation_sender: WakeSender<super::ActivationToken>,
@@ -160,7 +150,7 @@ impl UnownedWindow {
let xconn = &event_loop.xconn;
let atoms = xconn.atoms();
#[cfg(feature = "rwh_06")]
let root = match window_attrs.parent_window.0 {
let root = match window_attrs.parent_window {
Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
@@ -285,8 +275,7 @@ impl UnownedWindow {
| EventMask::KEYMAP_STATE
| EventMask::BUTTON_PRESS
| EventMask::BUTTON_RELEASE
| EventMask::POINTER_MOTION
| EventMask::PROPERTY_CHANGE;
| EventMask::POINTER_MOTION;
aux = aux.event_mask(event_mask).border_pixel(0);
@@ -360,7 +349,7 @@ impl UnownedWindow {
visual,
root,
screen_id,
selected_cursor: Default::default(),
cursor: Default::default(),
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
@@ -547,20 +536,21 @@ impl UnownedWindow {
.ignore_error();
{
if let Some(ime) = event_loop.ime.as_ref() {
let result = ime.borrow_mut().create_context(window.xwindow, false, None);
leap!(result);
}
let result = event_loop
.ime
.borrow_mut()
.create_context(window.xwindow as ffi::Window, false);
leap!(result);
}
// These properties must be set after mapping
if window_attrs.maximized {
leap!(window.set_maximized_inner(window_attrs.maximized)).ignore_error();
}
if window_attrs.fullscreen.0.is_some() {
if window_attrs.fullscreen.is_some() {
if let Some(flusher) =
leap!(window
.set_fullscreen_inner(window_attrs.fullscreen.0.clone().map(Into::into)))
.set_fullscreen_inner(window_attrs.fullscreen.clone().map(Into::into)))
{
flusher.ignore_error()
}
@@ -932,51 +922,6 @@ impl UnownedWindow {
})
}
/// Refresh the API for the given monitor.
#[inline]
pub(super) fn refresh_dpi_for_monitor<T: 'static>(
&self,
new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>,
mut callback: impl FnMut(Event<T>),
) {
// Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone();
if monitor.name == new_monitor.name {
let (width, height) = self.inner_size_physical();
let (new_width, new_height) = self.adjust_for_dpi(
// If we couldn't determine the previous scale
// factor (e.g., because all monitors were closed
// before), just pick whatever the current monitor
// has set as a baseline.
maybe_prev_scale_factor.unwrap_or(monitor.scale_factor),
new_monitor.scale_factor,
width,
height,
&self.shared_state_lock(),
);
let window_id = crate::window::WindowId(self.id());
let old_inner_size = PhysicalSize::new(width, height);
let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)),
},
});
let new_inner_size = *inner_size.lock().unwrap();
drop(inner_size);
if new_inner_size != old_inner_size {
let (new_width, new_height) = new_inner_size.into();
self.request_inner_size_physical(new_width, new_height);
}
}
}
fn set_minimized_inner(&self, minimized: bool) -> Result<VoidCookie<'_>, X11Error> {
let atoms = self.xconn.atoms();
@@ -1350,8 +1295,8 @@ impl UnownedWindow {
self.xconn
.flush_requests()
.expect("Failed to call XResizeWindow");
// cursor_hittest needs to be reapplied after each window resize.
if self.shared_state_lock().cursor_hittest.unwrap_or(false) {
// cursor_hittest needs to be reapplied after window resize
if self.shared_state_lock().cursor_hittest {
let _ = self.set_cursor_hittest(true);
}
}
@@ -1381,8 +1326,7 @@ impl UnownedWindow {
self.xwindow as xproto::Window,
xproto::AtomEnum::WM_NORMAL_HINTS,
)?
.reply()?
.unwrap_or_default();
.reply()?;
callback(&mut normal_hints);
normal_hints
.set(
@@ -1435,7 +1379,6 @@ impl UnownedWindow {
)
.ok()
.and_then(|cookie| cookie.reply().ok())
.flatten()
.and_then(|hints| hints.size_increment)
.map(|(width, height)| (width as u32, height as u32).into())
}
@@ -1539,29 +1482,13 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let old_cursor = replace(
&mut *self.selected_cursor.lock().unwrap(),
SelectedCursor::Named(cursor),
);
let old_cursor = replace(&mut *self.cursor.lock().unwrap(), cursor);
#[allow(clippy::mutex_atomic)]
if SelectedCursor::Named(cursor) != old_cursor && *self.cursor_visible.lock().unwrap() {
if cursor != old_cursor && *self.cursor_visible.lock().unwrap() {
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
}
}
#[inline]
pub fn set_custom_cursor(&self, cursor: RootCustomCursor) {
let new_cursor = unsafe { CustomCursor::new(&self.xconn, &cursor.inner) };
#[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() {
self.xconn.set_custom_cursor(self.xwindow, &new_cursor);
}
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(new_cursor);
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
@@ -1648,23 +1575,13 @@ impl UnownedWindow {
return;
}
let cursor = if visible {
Some((*self.selected_cursor.lock().unwrap()).clone())
Some(*self.cursor.lock().unwrap())
} else {
None
};
*visible_lock = visible;
drop(visible_lock);
match cursor {
Some(SelectedCursor::Custom(cursor)) => {
self.xconn.set_custom_cursor(self.xwindow, &cursor);
}
Some(SelectedCursor::Named(cursor)) => {
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
}
None => {
self.xconn.set_cursor_icon(self.xwindow, None);
}
}
self.xconn.set_cursor_icon(self.xwindow, cursor);
}
#[inline]
@@ -1710,7 +1627,7 @@ impl UnownedWindow {
.xcb_connection()
.xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region())
.map_err(|_e| ExternalError::Ignored)?;
self.shared_state_lock().cursor_hittest = Some(hittest);
self.shared_state_lock().cursor_hittest = hittest;
Ok(())
}
@@ -1774,8 +1691,8 @@ impl UnownedWindow {
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
),
[
(window.x as u32 + xinput_fp1616_to_float(pointer.win_x) as u32),
(window.y as u32 + xinput_fp1616_to_float(pointer.win_y) as u32),
(window.x as u32 + pointer.win_x as u32),
(window.y as u32 + pointer.win_y as u32),
action.try_into().unwrap(),
1, // Button 1
1,
@@ -1791,11 +1708,11 @@ impl UnownedWindow {
#[inline]
pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) {
let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
let _ = self
.ime_sender
.lock()
.unwrap()
.send(ImeRequest::Position(self.xwindow, x, y));
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position(
self.xwindow as ffi::Window,
x,
y,
));
}
#[inline]
@@ -1804,7 +1721,7 @@ impl UnownedWindow {
.ime_sender
.lock()
.unwrap()
.send(ImeRequest::Allow(self.xwindow, allowed));
.send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed));
}
#[inline]
@@ -1817,9 +1734,9 @@ impl UnownedWindow {
let state_type_atom = atoms[CARD32];
let is_minimized = if let Ok(state) =
self.xconn
.get_property::<u32>(self.xwindow, state_atom, state_type_atom)
.get_property(self.xwindow, state_atom, state_type_atom)
{
state.contains(&super::ICONIC_STATE)
state.contains(&(ffi::IconicState as c_ulong))
} else {
false
};
@@ -1856,7 +1773,6 @@ impl UnownedWindow {
WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
.ok()
.and_then(|cookie| cookie.reply().ok())
.flatten()
.unwrap_or_default();
wm_hints.urgent = request_type.is_some();

View File

@@ -91,14 +91,6 @@ impl XConnection {
conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))?
};
// Make sure Xlib knows XCB is handling events.
unsafe {
(xlib_xcb.XSetEventQueueOwner)(
display,
x11_dl::xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue,
);
}
// Get the default screen.
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;

View File

@@ -329,7 +329,7 @@ impl Handler {
) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let scale_factor_changed_event = Event::WindowEvent {
let event = Event::WindowEvent {
window_id: WindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
@@ -337,19 +337,13 @@ impl Handler {
},
};
callback.handle_nonuser_event(scale_factor_changed_event);
callback.handle_nonuser_event(event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
let resized_event = Event::WindowEvent {
window_id: WindowId(window.id()),
event: WindowEvent::Resized(physical_size),
};
callback.handle_nonuser_event(resized_event);
}
}
}
@@ -500,9 +494,9 @@ impl AppState {
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking()
|| HANDLER.get_in_callback()
|| !HANDLER.have_callback()
|| !HANDLER.is_running()
|| HANDLER.get_in_callback()
{
return;
}
@@ -607,9 +601,9 @@ impl AppState {
// XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're
// about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking()
|| HANDLER.get_in_callback()
|| !HANDLER.have_callback()
|| !HANDLER.is_running()
|| HANDLER.get_in_callback()
{
return;
}

View File

@@ -80,9 +80,6 @@ extern_methods!(
#[method(setMainMenu:)]
pub fn setMainMenu(&self, menu: &NSMenu);
#[method(setServicesMenu:)]
pub fn setServicesMenu(&self, menu: &NSMenu);
#[method_id(effectiveAppearance)]
pub fn effectiveAppearance(&self) -> Id<NSAppearance>;

View File

@@ -1,56 +0,0 @@
use std::ffi::c_uchar;
use icrate::Foundation::{NSInteger, NSObject, NSString};
use objc2::rc::Id;
use objc2::runtime::Bool;
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NSImageRep;
unsafe impl ClassType for NSImageRep {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern "C" {
static NSDeviceRGBColorSpace: &'static NSString;
}
extern_class!(
// <https://developer.apple.com/documentation/appkit/nsbitmapimagerep?language=objc>
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSBitmapImageRep;
unsafe impl ClassType for NSBitmapImageRep {
type Super = NSImageRep;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl NSBitmapImageRep {
pub fn init_rgba(width: NSInteger, height: NSInteger) -> Id<Self> {
unsafe {
msg_send_id![Self::alloc(),
initWithBitmapDataPlanes: std::ptr::null_mut::<*mut c_uchar>(),
pixelsWide: width,
pixelsHigh: height,
bitsPerSample: 8 as NSInteger,
samplesPerPixel: 4 as NSInteger,
hasAlpha: Bool::new(true),
isPlanar: Bool::new(false),
colorSpaceName: NSDeviceRGBColorSpace,
bytesPerRow: width * 4,
bitsPerPixel: 32 as NSInteger,
]
}
}
pub fn bitmap_data(&self) -> *mut u8 {
unsafe { msg_send![self, bitmapData] }
}
}
);

View File

@@ -2,14 +2,13 @@ use once_cell::sync::Lazy;
use icrate::ns_string;
use icrate::Foundation::{
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString,
NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSString,
};
use objc2::rc::{DefaultId, Id};
use objc2::runtime::Sel;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType};
use super::{NSBitmapImageRep, NSImage};
use crate::cursor::CursorImage;
use super::NSImage;
use crate::window::CursorIcon;
extern_class!(
@@ -233,23 +232,6 @@ impl NSCursor {
_ => Default::default(),
}
}
pub fn from_image(cursor: &CursorImage) -> Id<Self> {
let width = cursor.width;
let height = cursor.height;
let bitmap = NSBitmapImageRep::init_rgba(width as isize, height as isize);
let bitmap_data =
unsafe { std::slice::from_raw_parts_mut(bitmap.bitmap_data(), cursor.rgba.len()) };
bitmap_data.copy_from_slice(&cursor.rgba);
let image = NSImage::init_with_size(NSSize::new(width.into(), height.into()));
image.add_representation(&bitmap);
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
NSCursor::new(&image, hotspot)
}
}
impl DefaultId for NSCursor {

View File

@@ -1,8 +1,6 @@
use icrate::Foundation::{NSData, NSObject, NSSize, NSString};
use icrate::Foundation::{NSData, NSObject, NSString};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
use super::NSBitmapImageRep;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
extern_class!(
// TODO: Can this be mutable?
@@ -34,13 +32,5 @@ extern_methods!(
pub fn new_with_data(data: &NSData) -> Id<Self> {
unsafe { msg_send_id![Self::alloc(), initWithData: data] }
}
pub fn init_with_size(size: NSSize) -> Id<Self> {
unsafe { msg_send_id![Self::alloc(), initWithSize: size] }
}
pub fn add_representation(&self, representation: &NSBitmapImageRep) {
unsafe { msg_send![self, addRepresentation: representation] }
}
}
);

View File

@@ -20,11 +20,7 @@ extern_methods!(
#[method_id(new)]
pub fn new() -> Id<Self>;
pub fn newWithTitle(
title: &NSString,
action: Option<Sel>,
key_equivalent: &NSString,
) -> Id<Self> {
pub fn newWithTitle(title: &NSString, action: Sel, key_equivalent: &NSString) -> Id<Self> {
unsafe {
msg_send_id![
Self::alloc(),

View File

@@ -13,7 +13,6 @@
mod appearance;
mod application;
mod bitmap_image_rep;
mod button;
mod color;
mod control;
@@ -37,7 +36,6 @@ pub(crate) use self::application::{
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
NSRequestUserAttentionType,
};
pub(crate) use self::bitmap_image_rep::NSBitmapImageRep;
pub(crate) use self::button::NSButton;
pub(crate) use self::color::NSColor;
pub(crate) use self::control::NSControl;

View File

@@ -23,7 +23,7 @@ extern_methods!(
pub fn selectPreviousTab(&self);
#[method_id(windows)]
pub fn tabbedWindows(&self) -> Option<Id<NSArray<NSWindow>>>;
pub fn tabbedWindows(&self) -> Id<NSArray<NSWindow>>;
#[method(setSelectedWindow:)]
pub fn setSelectedWindow(&self, window: &NSWindow);

View File

@@ -36,9 +36,6 @@ extern_methods!(
#[method(frame)]
pub(crate) fn frame(&self) -> NSRect;
#[method(windowNumber)]
pub(crate) fn windowNumber(&self) -> NSInteger;
#[method(backingScaleFactor)]
pub(crate) fn backingScaleFactor(&self) -> CGFloat;
@@ -221,7 +218,7 @@ extern_methods!(
pub(crate) fn tabbingIdentifier(&self) -> Id<NSString>;
#[method_id(tabGroup)]
pub(crate) fn tabGroup(&self) -> Option<Id<NSWindowTabGroup>>;
pub(crate) fn tabGroup(&self) -> Id<NSWindowTabGroup>;
#[method(isDocumentEdited)]
pub(crate) fn isDocumentEdited(&self) -> bool;

View File

@@ -11,12 +11,9 @@ use super::appkit::{NSEvent, NSEventModifierFlags};
use crate::{
event::{ElementState, KeyEvent, Modifiers},
keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey,
NativeKeyCode, PhysicalKey,
},
platform::{
modifier_supplement::KeyEventExtModifierSupplement, scancode::PhysicalKeyExtScancode,
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
},
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
platform_impl::platform::ffi,
};
@@ -88,9 +85,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
return Key::Unidentified(NativeKey::MacOS(scancode));
}
if result_len == 0 {
// This is fine - not all keys have text representation.
// For instance, users that have mapped the `Fn` key to toggle
// keyboard layouts will hit this code path.
log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length.");
return Key::Unidentified(NativeKey::MacOS(scancode));
}
let chars = String::from_utf16_lossy(&string[0..result_len as usize]);
@@ -117,14 +112,13 @@ pub(crate) fn create_key_event(
ns_event: &NSEvent,
is_press: bool,
is_repeat: bool,
key_override: Option<PhysicalKey>,
key_override: Option<KeyCode>,
) -> KeyEvent {
use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released };
let scancode = ns_event.key_code();
let mut physical_key =
key_override.unwrap_or_else(|| PhysicalKey::from_scancode(scancode as u32));
let mut physical_key = key_override.unwrap_or_else(|| KeyCode::from_scancode(scancode as u32));
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
None
@@ -136,7 +130,7 @@ pub(crate) fn create_key_event(
if characters.is_empty() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
if matches!(physical_key, KeyCode::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
@@ -158,11 +152,13 @@ pub(crate) fn create_key_event(
// Also not checking if this is a release event because then this issue would
// still affect the key release.
Some(text) if !has_ctrl => Key::Character(text.clone()),
_ => match key_without_modifiers.as_ref() {
Key::Character(ch) => get_logical_key_char(ns_event, ch),
// Don't try to get text for events which likely don't have it.
_ => key_without_modifiers.clone(),
},
_ => {
let modifierless_chars = match key_without_modifiers.as_ref() {
Key::Character(ch) => ch,
_ => "",
};
get_logical_key_char(ns_event, modifierless_chars)
}
};
(logical_key, key_without_modifiers)
@@ -192,75 +188,65 @@ pub(crate) fn create_key_event(
}
}
pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
let code = match key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()),
};
pub fn code_to_key(code: KeyCode, scancode: u16) -> Key {
match code {
KeyCode::Enter => Key::Enter,
KeyCode::Tab => Key::Tab,
KeyCode::Space => Key::Space,
KeyCode::Backspace => Key::Backspace,
KeyCode::Escape => Key::Escape,
KeyCode::SuperRight => Key::Super,
KeyCode::SuperLeft => Key::Super,
KeyCode::ShiftLeft => Key::Shift,
KeyCode::AltLeft => Key::Alt,
KeyCode::ControlLeft => Key::Control,
KeyCode::ShiftRight => Key::Shift,
KeyCode::AltRight => Key::Alt,
KeyCode::ControlRight => Key::Control,
Key::Named(match code {
KeyCode::Enter => NamedKey::Enter,
KeyCode::Tab => NamedKey::Tab,
KeyCode::Space => NamedKey::Space,
KeyCode::Backspace => NamedKey::Backspace,
KeyCode::Escape => NamedKey::Escape,
KeyCode::SuperRight => NamedKey::Super,
KeyCode::SuperLeft => NamedKey::Super,
KeyCode::ShiftLeft => NamedKey::Shift,
KeyCode::AltLeft => NamedKey::Alt,
KeyCode::ControlLeft => NamedKey::Control,
KeyCode::ShiftRight => NamedKey::Shift,
KeyCode::AltRight => NamedKey::Alt,
KeyCode::ControlRight => NamedKey::Control,
KeyCode::NumLock => NamedKey::NumLock,
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
KeyCode::NumLock => Key::NumLock,
KeyCode::AudioVolumeUp => Key::AudioVolumeUp,
KeyCode::AudioVolumeDown => Key::AudioVolumeDown,
// Other numpad keys all generate text on macOS (if I understand correctly)
KeyCode::NumpadEnter => NamedKey::Enter,
KeyCode::NumpadEnter => Key::Enter,
KeyCode::F1 => NamedKey::F1,
KeyCode::F2 => NamedKey::F2,
KeyCode::F3 => NamedKey::F3,
KeyCode::F4 => NamedKey::F4,
KeyCode::F5 => NamedKey::F5,
KeyCode::F6 => NamedKey::F6,
KeyCode::F7 => NamedKey::F7,
KeyCode::F8 => NamedKey::F8,
KeyCode::F9 => NamedKey::F9,
KeyCode::F10 => NamedKey::F10,
KeyCode::F11 => NamedKey::F11,
KeyCode::F12 => NamedKey::F12,
KeyCode::F13 => NamedKey::F13,
KeyCode::F14 => NamedKey::F14,
KeyCode::F15 => NamedKey::F15,
KeyCode::F16 => NamedKey::F16,
KeyCode::F17 => NamedKey::F17,
KeyCode::F18 => NamedKey::F18,
KeyCode::F19 => NamedKey::F19,
KeyCode::F20 => NamedKey::F20,
KeyCode::F1 => Key::F1,
KeyCode::F2 => Key::F2,
KeyCode::F3 => Key::F3,
KeyCode::F4 => Key::F4,
KeyCode::F5 => Key::F5,
KeyCode::F6 => Key::F6,
KeyCode::F7 => Key::F7,
KeyCode::F8 => Key::F8,
KeyCode::F9 => Key::F9,
KeyCode::F10 => Key::F10,
KeyCode::F11 => Key::F11,
KeyCode::F12 => Key::F12,
KeyCode::F13 => Key::F13,
KeyCode::F14 => Key::F14,
KeyCode::F15 => Key::F15,
KeyCode::F16 => Key::F16,
KeyCode::F17 => Key::F17,
KeyCode::F18 => Key::F18,
KeyCode::F19 => Key::F19,
KeyCode::F20 => Key::F20,
KeyCode::Insert => NamedKey::Insert,
KeyCode::Home => NamedKey::Home,
KeyCode::PageUp => NamedKey::PageUp,
KeyCode::Delete => NamedKey::Delete,
KeyCode::End => NamedKey::End,
KeyCode::PageDown => NamedKey::PageDown,
KeyCode::ArrowLeft => NamedKey::ArrowLeft,
KeyCode::ArrowRight => NamedKey::ArrowRight,
KeyCode::ArrowDown => NamedKey::ArrowDown,
KeyCode::ArrowUp => NamedKey::ArrowUp,
_ => return Key::Unidentified(NativeKey::MacOS(scancode)),
})
KeyCode::Insert => Key::Insert,
KeyCode::Home => Key::Home,
KeyCode::PageUp => Key::PageUp,
KeyCode::Delete => Key::Delete,
KeyCode::End => Key::End,
KeyCode::PageDown => Key::PageDown,
KeyCode::ArrowLeft => Key::ArrowLeft,
KeyCode::ArrowRight => Key::ArrowRight,
KeyCode::ArrowDown => Key::ArrowDown,
KeyCode::ArrowUp => Key::ArrowUp,
_ => Key::Unidentified(NativeKey::MacOS(scancode)),
}
}
pub fn code_to_location(key: PhysicalKey) -> KeyLocation {
let code = match key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => return KeyLocation::Standard,
};
pub fn code_to_location(code: KeyCode) -> KeyLocation {
match code {
KeyCode::SuperRight => KeyLocation::Right,
KeyCode::SuperLeft => KeyLocation::Left,
@@ -297,17 +283,17 @@ pub fn code_to_location(key: PhysicalKey) -> KeyLocation {
// While F1-F20 have scancodes we can match on, we have to check against UTF-16
// constants for the rest.
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ
pub fn extra_function_key_to_code(scancode: u16, string: &str) -> PhysicalKey {
pub fn extra_function_key_to_code(scancode: u16, string: &str) -> KeyCode {
if let Some(ch) = string.encode_utf16().next() {
match ch {
0xf718 => PhysicalKey::Code(KeyCode::F21),
0xf719 => PhysicalKey::Code(KeyCode::F22),
0xf71a => PhysicalKey::Code(KeyCode::F23),
0xf71b => PhysicalKey::Code(KeyCode::F24),
_ => PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode)),
0xf718 => KeyCode::F21,
0xf719 => KeyCode::F22,
0xf71a => KeyCode::F23,
0xf71b => KeyCode::F24,
_ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode)),
}
} else {
PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode))
KeyCode::Unidentified(NativeKeyCode::MacOS(scancode))
}
}
@@ -354,14 +340,9 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
}
}
impl PhysicalKeyExtScancode for PhysicalKey {
impl KeyCodeExtScancode for KeyCode {
fn to_scancode(self) -> Option<u32> {
let code = match self {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => return None,
};
match code {
match self {
KeyCode::KeyA => Some(0x00),
KeyCode::KeyS => Some(0x01),
KeyCode::KeyD => Some(0x02),
@@ -477,8 +458,8 @@ impl PhysicalKeyExtScancode for PhysicalKey {
}
}
fn from_scancode(scancode: u32) -> PhysicalKey {
PhysicalKey::Code(match scancode {
fn from_scancode(scancode: u32) -> KeyCode {
match scancode {
0x00 => KeyCode::KeyA,
0x01 => KeyCode::KeyS,
0x02 => KeyCode::KeyD,
@@ -615,7 +596,7 @@ impl PhysicalKeyExtScancode for PhysicalKey {
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
// backquote (`) on Windows' US layout.
0xa => KeyCode::Backquote,
_ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
})
_ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
}
}
}

View File

@@ -11,7 +11,6 @@ use core_graphics::{
base::CGError,
display::{CGDirectDisplayID, CGDisplayConfigRef},
};
use objc2::{ffi::NSInteger, runtime::AnyObject};
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
@@ -114,14 +113,6 @@ extern "C" {
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
// Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius(
connection_id: *mut AnyObject,
window_id: NSInteger,
radius: i64,
) -> i32;
}
mod core_video {

View File

@@ -21,16 +21,7 @@ pub fn initialize() {
// About menu item
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
let about_item = menu_item(
&about_item_title,
Some(sel!(orderFrontStandardAboutPanel:)),
None,
);
// Services menu item
let services_menu = NSMenu::new();
let services_item = menu_item(ns_string!("Services"), None, None);
services_item.setSubmenu(&services_menu);
let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None);
// Seperator menu item
let sep_first = NSMenuItem::separatorItem();
@@ -39,7 +30,7 @@ pub fn initialize() {
let hide_item_title = ns_string!("Hide ").stringByAppendingString(&process_name);
let hide_item = menu_item(
&hide_item_title,
Some(sel!(hide:)),
sel!(hide:),
Some(KeyEquivalent {
key: ns_string!("h"),
masks: None,
@@ -50,7 +41,7 @@ pub fn initialize() {
let hide_others_item_title = ns_string!("Hide Others");
let hide_others_item = menu_item(
hide_others_item_title,
Some(sel!(hideOtherApplications:)),
sel!(hideOtherApplications:),
Some(KeyEquivalent {
key: ns_string!("h"),
masks: Some(
@@ -61,11 +52,7 @@ pub fn initialize() {
// Show applications menu item
let show_all_item_title = ns_string!("Show All");
let show_all_item = menu_item(
show_all_item_title,
Some(sel!(unhideAllApplications:)),
None,
);
let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None);
// Seperator menu item
let sep = NSMenuItem::separatorItem();
@@ -74,7 +61,7 @@ pub fn initialize() {
let quit_item_title = ns_string!("Quit ").stringByAppendingString(&process_name);
let quit_item = menu_item(
&quit_item_title,
Some(sel!(terminate:)),
sel!(terminate:),
Some(KeyEquivalent {
key: ns_string!("q"),
masks: None,
@@ -83,7 +70,6 @@ pub fn initialize() {
app_menu.addItem(&about_item);
app_menu.addItem(&sep_first);
app_menu.addItem(&services_item);
app_menu.addItem(&hide_item);
app_menu.addItem(&hide_others_item);
app_menu.addItem(&show_all_item);
@@ -92,13 +78,12 @@ pub fn initialize() {
app_menu_item.setSubmenu(&app_menu);
let app = NSApp();
app.setServicesMenu(&services_menu);
app.setMainMenu(&menubar);
}
fn menu_item(
title: &NSString,
selector: Option<Sel>,
selector: Sel,
key_equivalent: Option<KeyEquivalent<'_>>,
) -> Id<NSMenuItem> {
let (key, masks) = match key_equivalent {

View File

@@ -28,7 +28,6 @@ pub(crate) use self::{
use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::window::Window;
pub(crate) use crate::cursor::CursorImage as PlatformCustomCursor;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View File

@@ -7,9 +7,7 @@ use core_foundation::{
base::{CFRelease, TCFType},
string::CFString,
};
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use objc2::rc::Id;
use super::appkit::NSScreen;
@@ -218,12 +216,6 @@ impl MonitorHandle {
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
unsafe {
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
if refresh_rate > 0.0 {
return Some((refresh_rate * 1000.0).round() as u32);
}
let mut display_link = std::ptr::null_mut();
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
!= ffi::kCVReturnSuccess

View File

@@ -26,9 +26,9 @@ use crate::{
DeviceEvent, ElementState, Event, Ime, Modifiers, MouseButton, MouseScrollDelta,
TouchPhase, WindowEvent,
},
keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey},
keyboard::{Key, KeyCode, KeyLocation, ModifiersState},
platform::macos::{OptionAsAlt, WindowExtMacOS},
platform::scancode::PhysicalKeyExtScancode,
platform::scancode::KeyCodeExtScancode,
platform_impl::platform::{
app_state::AppState,
event::{create_key_event, event_mods},
@@ -74,8 +74,8 @@ enum ImeState {
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq)]
struct ModLocationMask: u8 {
const LEFT = 0b0001;
const RIGHT = 0b0010;
const LEFT = 1;
const RIGHT = 2;
}
}
impl ModLocationMask {
@@ -88,32 +88,32 @@ impl ModLocationMask {
}
}
fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
fn key_to_modifier(key: &Key) -> ModifiersState {
match key {
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
_ => None,
Key::Alt => ModifiersState::ALT,
Key::Control => ModifiersState::CONTROL,
Key::Super => ModifiersState::SUPER,
Key::Shift => ModifiersState::SHIFT,
_ => unreachable!(),
}
}
fn get_right_modifier_code(key: &Key) -> KeyCode {
match key {
Key::Named(NamedKey::Alt) => KeyCode::AltRight,
Key::Named(NamedKey::Control) => KeyCode::ControlRight,
Key::Named(NamedKey::Shift) => KeyCode::ShiftRight,
Key::Named(NamedKey::Super) => KeyCode::SuperRight,
Key::Alt => KeyCode::AltRight,
Key::Control => KeyCode::ControlRight,
Key::Shift => KeyCode::ShiftRight,
Key::Super => KeyCode::SuperRight,
_ => unreachable!(),
}
}
fn get_left_modifier_code(key: &Key) -> KeyCode {
match key {
Key::Named(NamedKey::Alt) => KeyCode::AltLeft,
Key::Named(NamedKey::Control) => KeyCode::ControlLeft,
Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft,
Key::Named(NamedKey::Super) => KeyCode::SuperLeft,
Key::Alt => KeyCode::AltLeft,
Key::Control => KeyCode::ControlLeft,
Key::Shift => KeyCode::ShiftLeft,
Key::Super => KeyCode::SuperLeft,
_ => unreachable!(),
}
}
@@ -918,102 +918,92 @@ impl WinitView {
// This function was called form the flagsChanged event, which is triggered
// when the user presses/releases a modifier even if the same kind of modifier
// has already been pressed.
//
// When flags changed event has key code of zero it means that event doesn't carry any key
// event, thus we can't generate regular presses based on that. The `ModifiersChanged`
// later will work though, since the flags are attached to the event and contain valid
// information.
'send_event: {
if is_flags_changed_event && ns_event.key_code() != 0 {
let scancode = ns_event.key_code();
let physical_key = PhysicalKey::from_scancode(scancode as u32);
// has already been pressed
if is_flags_changed_event {
let scancode = ns_event.key_code();
let keycode = KeyCode::from_scancode(scancode as u32);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(keycode));
let key = code_to_key(physical_key, scancode);
// Ignore processing of unkown modifiers because we can't determine whether
// it was pressed or release reliably.
let Some(event_modifier) = key_to_modifier(&key) else {
break 'send_event;
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let location_mask = ModLocationMask::from_location(event.location);
let key = code_to_key(keycode, scancode);
let event_modifier = key_to_modifier(&key);
event.physical_key = keycode;
event.logical_key = key.clone();
event.location = code_to_location(keycode);
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state
.entry(key)
.or_insert(ModLocationMask::empty());
let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state
.entry(key)
.or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);
// There is no API for getting whether the button was pressed or released
// during this event. For this reason we have to do a bit of magic below
// to come up with a good guess whether this key was pressed or released.
// (This is not trivial because there are multiple buttons that may affect
// the same modifier)
if !is_active {
event.state = Released;
if phys_mod.contains(ModLocationMask::LEFT) {
let mut event = event.clone();
event.location = KeyLocation::Left;
event.physical_key = get_left_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
if phys_mod.contains(ModLocationMask::RIGHT) {
event.location = KeyLocation::Right;
event.physical_key = get_right_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
*phys_mod = ModLocationMask::empty();
} else {
if *phys_mod == location_mask {
// Here we hit a contradiction:
// The modifier state was "changed" to active,
// yet the only pressed modifier key was the one that we
// just got a change event for.
// This seemingly means that the only pressed modifier is now released,
// but at the same time the modifier became active.
//
// But this scenario is possible if we released modifiers
// while the application was not in focus. (Because we don't
// get informed of modifier key events while the application
// is not focused)
// In this case we prioritize the information
// about the current modifier state which means
// that the button was pressed.
event.state = Pressed;
} else {
phys_mod.toggle(location_mask);
let is_pressed = phys_mod.contains(location_mask);
event.state = if is_pressed { Pressed } else { Released };
}
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);
// There is no API for getting whether the button was pressed or released
// during this event. For this reason we have to do a bit of magic below
// to come up with a good guess whether this key was pressed or released.
// (This is not trivial because there are multiple buttons that may affect
// the same modifier)
if !is_active {
event.state = Released;
if phys_mod.contains(ModLocationMask::LEFT) {
let mut event = event.clone();
event.location = KeyLocation::Left;
event.physical_key = get_left_modifier_code(&event.logical_key);
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
drop(phys_mod_state);
for event in events {
self.queue_event(event);
if phys_mod.contains(ModLocationMask::RIGHT) {
event.location = KeyLocation::Right;
event.physical_key = get_right_modifier_code(&event.logical_key);
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
*phys_mod = ModLocationMask::empty();
} else {
// is_active
if *phys_mod == location_mask {
// Here we hit a contradiction:
// The modifier state was "changed" to active,
// yet the only pressed modifier key was the one that we
// just got a change event for.
// This seemingly means that the only pressed modifier is now released,
// but at the same time the modifier became active.
//
// But this scenario is possible if we released modifiers
// while the application was not in focus. (Because we don't
// get informed of modifier key events while the application
// is not focused)
// In this case we prioritize the information
// about the current modifier state which means
// that the button was pressed.
event.state = Pressed;
} else {
phys_mod.toggle(location_mask);
let is_pressed = phys_mod.contains(location_mask);
event.state = if is_pressed { Pressed } else { Released };
}
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
drop(phys_mod_state);
for event in events {
self.queue_event(event);
}
}

View File

@@ -7,7 +7,6 @@ use std::os::raw::c_void;
use std::ptr::NonNull;
use std::sync::{Mutex, MutexGuard};
use crate::cursor::CustomCursor;
use crate::{
dpi::{
LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical,
@@ -47,8 +46,6 @@ use super::appkit::{
NSView, NSWindow, NSWindowButton, NSWindowLevel, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility,
};
use super::ffi::CGSMainConnectionID;
use super::ffi::CGSSetWindowBackgroundBlurRadius;
pub(crate) struct Window {
window: MainThreadBound<Id<WinitWindow>>,
@@ -298,7 +295,7 @@ impl WinitWindow {
trace_scope!("WinitWindow::new");
let this = autoreleasepool(|_| {
let screen = match attrs.fullscreen.0.clone().map(Into::into) {
let screen = match attrs.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Borderless(Some(monitor)))
| Some(Fullscreen::Exclusive(VideoMode { monitor, .. })) => {
monitor.ns_screen().or_else(NSScreen::main)
@@ -452,7 +449,7 @@ impl WinitWindow {
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?;
#[cfg(feature = "rwh_06")]
match attrs.parent_window.0 {
match attrs.parent_window {
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
// SAFETY: Caller ensures the pointer is valid or NULL
// Unwrap is fine, since the pointer comes from `NonNull`.
@@ -497,10 +494,6 @@ impl WinitWindow {
this.setBackgroundColor(&NSColor::clear());
}
if attrs.blur {
this.set_blur(attrs.blur);
}
if let Some(dim) = attrs.min_inner_size {
this.set_min_inner_size(Some(dim));
}
@@ -527,14 +520,14 @@ impl WinitWindow {
}
}
let delegate = WinitWindowDelegate::new(&this, attrs.fullscreen.0.is_some());
let delegate = WinitWindowDelegate::new(&this, attrs.fullscreen.is_some());
// XXX Send `Focused(false)` right after creating the window delegate, so we won't
// obscure the real focused events on the startup.
delegate.queue_event(WindowEvent::Focused(false));
// Set fullscreen mode after we setup everything
this.set_fullscreen(attrs.fullscreen.0.map(Into::into));
this.set_fullscreen(attrs.fullscreen.map(Into::into));
// Setting the window as key has to happen *after* we set the fullscreen
// state, since otherwise we'll briefly see the window at normal size
@@ -589,15 +582,7 @@ impl WinitWindow {
self.setOpaque(!transparent)
}
pub fn set_blur(&self, blur: bool) {
// NOTE: in general we want to specify the blur radius, but the choice of 80
// should be a reasonable default.
let radius = if blur { 80 } else { 0 };
let window_number = self.windowNumber();
unsafe {
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius);
}
}
pub fn set_blur(&self, _blur: bool) {}
pub fn set_visible(&self, visible: bool) {
match visible {
@@ -835,13 +820,6 @@ impl WinitWindow {
self.invalidateCursorRectsForView(&view);
}
#[inline]
pub fn set_custom_cursor(&self, cursor: CustomCursor) {
let view = self.view();
view.set_cursor_icon(NSCursor::from_image(&cursor.inner));
self.invalidateCursorRectsForView(&view);
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let associate_mouse_cursor = match mode {
@@ -1547,35 +1525,27 @@ impl WindowExtMacOS for WinitWindow {
#[inline]
fn select_next_tab(&self) {
if let Some(group) = self.tabGroup() {
group.selectNextTab();
}
self.tabGroup().selectNextTab();
}
#[inline]
fn select_previous_tab(&self) {
if let Some(group) = self.tabGroup() {
group.selectPreviousTab()
}
self.tabGroup().selectPreviousTab();
}
#[inline]
fn select_tab_at_index(&self, index: usize) {
if let Some(group) = self.tabGroup() {
if let Some(windows) = group.tabbedWindows() {
if index < windows.len() {
group.setSelectedWindow(&windows[index]);
}
}
let tab_group = self.tabGroup();
let windows = tab_group.tabbedWindows();
if index < windows.len() {
tab_group.setSelectedWindow(&windows[index]);
}
}
#[inline]
fn num_tabs(&self) -> usize {
self.tabGroup()
.and_then(|group| group.tabbedWindows())
.map(|windows| windows.len())
.unwrap_or(1)
let tab_group = self.tabGroup();
tab_group.tabbedWindows().len()
}
fn is_document_edited(&self) -> bool {

View File

@@ -18,7 +18,6 @@ use crate::{
event_loop::{self, ControlFlow, DeviceEvents},
keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
PhysicalKey,
},
window::WindowId as RootWindowId,
};
@@ -28,8 +27,8 @@ use super::{
RedoxSocket, TimeSocket, WindowId, WindowProperties,
};
fn convert_scancode(scancode: u8) -> PhysicalKey {
PhysicalKey::Code(match scancode {
fn convert_scancode(scancode: u8) -> KeyCode {
match scancode {
orbclient::K_A => KeyCode::KeyA,
orbclient::K_B => KeyCode::KeyB,
orbclient::K_C => KeyCode::KeyC,
@@ -110,8 +109,8 @@ fn convert_scancode(scancode: u8) -> PhysicalKey {
orbclient::K_F11 => KeyCode::F11,
orbclient::K_F12 => KeyCode::F12,
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
})
_ => KeyCode::Unidentified(NativeKeyCode::Unidentified),
}
}
fn element_state(pressed: bool) -> event::ElementState {
@@ -153,12 +152,7 @@ struct EventState {
}
impl EventState {
fn key(&mut self, key: PhysicalKey, pressed: bool) {
let code = match key {
PhysicalKey::Code(code) => code,
_ => return,
};
fn key(&mut self, code: KeyCode, pressed: bool) {
match code {
KeyCode::ShiftLeft => self.keyboard.set(KeyboardModifierState::LSHIFT, pressed),
KeyCode::ShiftRight => self.keyboard.set(KeyboardModifierState::RSHIFT, pressed),
@@ -338,16 +332,16 @@ impl<T: 'static> EventLoop<T> {
pressed,
}) => {
if scancode != 0 {
let physical_key = convert_scancode(scancode);
let code = convert_scancode(scancode);
let modifiers_before = event_state.keyboard;
event_state.key(physical_key, pressed);
event_state.key(code, pressed);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key: Key::Unidentified(NativeKey::Unidentified),
physical_key,
physical_key: code,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,

View File

@@ -193,7 +193,6 @@ impl Display for OsError {
}
}
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]

View File

@@ -4,7 +4,6 @@ use std::{
};
use crate::{
cursor::CustomCursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
platform_impl::Fullscreen,
@@ -353,8 +352,6 @@ impl Window {
#[inline]
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub fn set_custom_cursor(&self, _: CustomCursor) {}
#[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(

View File

@@ -0,0 +1,297 @@
use atomic_waker::AtomicWaker;
use std::future;
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError};
use std::sync::{Arc, Condvar, Mutex, RwLock};
use std::task::Poll;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
// Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads.
// `value` **must** only be accessed on the main thread.
pub struct MainThreadSafe<const SYNC: bool, T: 'static, E: 'static> {
// We wrap this in an `Arc` to allow it to be safely cloned without accessing the value.
// The `RwLock` lets us safely drop in any thread.
// The `Option` lets us safely drop `T` only in the main thread, while letting other threads drop `None`.
value: Arc<RwLock<Option<T>>>,
handler: fn(&RwLock<Option<T>>, E),
sender: AsyncSender<E>,
// Prevent's `Send` or `Sync` to be automatically implemented.
local: PhantomData<*const ()>,
}
impl<const SYNC: bool, T, E> MainThreadSafe<SYNC, T, E> {
thread_local! {
static MAIN_THREAD: bool = {
#[wasm_bindgen]
extern "C" {
#[derive(Clone)]
type Global;
#[wasm_bindgen(method, getter, js_name = Window)]
fn window(this: &Global) -> JsValue;
}
let global: Global = js_sys::global().unchecked_into();
!global.window().is_undefined()
};
}
#[track_caller]
fn new(value: T, handler: fn(&RwLock<Option<T>>, E)) -> Option<Self> {
Self::MAIN_THREAD.with(|safe| {
if !safe {
panic!("only callable from inside the `Window`")
}
});
let value = Arc::new(RwLock::new(Some(value)));
let (sender, receiver) = channel::<E>();
wasm_bindgen_futures::spawn_local({
let value = Arc::clone(&value);
async move {
while let Ok(event) = receiver.next().await {
handler(&value, event)
}
// An error was returned because the channel was closed, which
// happens when all senders are dropped.
value.write().unwrap().take().unwrap();
}
});
Some(Self {
value,
handler,
sender,
local: PhantomData,
})
}
pub fn send(&self, event: E) {
Self::MAIN_THREAD.with(|is_main_thread| {
if *is_main_thread {
(self.handler)(&self.value, event)
} else {
self.sender.send(event).unwrap()
}
})
}
fn is_main_thread(&self) -> bool {
Self::MAIN_THREAD.with(|is_main_thread| *is_main_thread)
}
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
Self::MAIN_THREAD.with(|is_main_thread| {
if *is_main_thread {
Some(f(self.value.read().unwrap().as_ref().unwrap()))
} else {
None
}
})
}
}
impl<const SYNC: bool, T, E> Clone for MainThreadSafe<SYNC, T, E> {
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
handler: self.handler,
sender: self.sender.clone(),
local: PhantomData,
}
}
}
unsafe impl<const SYNC: bool, T, E> Send for MainThreadSafe<SYNC, T, E> {}
unsafe impl<T, E> Sync for MainThreadSafe<true, T, E> {}
fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) {
let (sender, receiver) = mpsc::channel();
let sender = Arc::new(Mutex::new(sender));
let waker = Arc::new(AtomicWaker::new());
let closed = Arc::new(AtomicBool::new(false));
let sender = AsyncSender {
sender,
closed: closed.clone(),
waker: Arc::clone(&waker),
};
let receiver = AsyncReceiver {
receiver,
closed,
waker,
};
(sender, receiver)
}
struct AsyncSender<T> {
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
// be accessed on the main thread, as it could block. Additionally we need
// to wrap it in an `Arc` to make it clonable on the main thread without
// having to block.
sender: Arc<Mutex<Sender<T>>>,
closed: Arc<AtomicBool>,
waker: Arc<AtomicWaker>,
}
impl<T> AsyncSender<T> {
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
self.sender.lock().unwrap().send(event)?;
self.waker.wake();
Ok(())
}
}
impl<T> Clone for AsyncSender<T> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
waker: self.waker.clone(),
closed: self.closed.clone(),
}
}
}
impl<T> Drop for AsyncSender<T> {
fn drop(&mut self) {
// If it's the last + the one held by the receiver make sure to wake it
// up and tell it that all receiver have dropped.
if Arc::strong_count(&self.closed) == 2 {
self.closed.store(true, Ordering::Relaxed);
self.waker.wake()
}
}
}
struct AsyncReceiver<T> {
receiver: Receiver<T>,
closed: Arc<AtomicBool>,
waker: Arc<AtomicWaker>,
}
impl<T> AsyncReceiver<T> {
pub async fn next(&self) -> Result<T, RecvError> {
future::poll_fn(|cx| match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => {
if self.closed.load(Ordering::Relaxed) {
return Poll::Ready(Err(RecvError));
}
self.waker.register(cx.waker());
match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => {
if self.closed.load(Ordering::Relaxed) {
Poll::Ready(Err(RecvError))
} else {
Poll::Pending
}
}
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
}
}
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
})
.await
}
}
pub struct Dispatcher<T: 'static>(MainThreadSafe<true, T, Closure<T>>);
pub struct Closure<T>(Box<dyn FnOnce(&T) + Send>);
impl<T> Dispatcher<T> {
#[track_caller]
pub fn new(value: T) -> Option<Self> {
MainThreadSafe::new(value, |value, Closure(closure)| {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
// funny with it here. See `Self::queue()`.
closure(value.read().unwrap().as_ref().unwrap())
})
.map(Self)
}
pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) {
if self.is_main_thread() {
self.0.with(f).unwrap()
} else {
self.0.send(Closure(Box::new(f)))
}
}
pub fn queue<R: Send>(&self, f: impl FnOnce(&T) -> R + Send) -> R {
if self.is_main_thread() {
self.0.with(f).unwrap()
} else {
let pair = Arc::new((Mutex::new(None), Condvar::new()));
let closure = Box::new({
let pair = pair.clone();
move |value: &T| {
*pair.0.lock().unwrap() = Some(f(value));
pair.1.notify_one();
}
}) as Box<dyn FnOnce(&T) + Send>;
// SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is
// safe because this function won't return until `f` has finished executing. See
// `Self::new()`.
let closure = Closure(unsafe { std::mem::transmute(closure) });
self.0.send(closure);
let mut started = pair.0.lock().unwrap();
while started.is_none() {
started = pair.1.wait(started).unwrap();
}
started.take().unwrap()
}
}
}
impl<T> Deref for Dispatcher<T> {
type Target = MainThreadSafe<true, T, Closure<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
type ChannelValue<T, E> = MainThreadSafe<false, (T, fn(&T, E)), E>;
pub struct Channel<T: 'static, E: 'static>(ChannelValue<T, E>);
impl<T, E> Channel<T, E> {
pub fn new(value: T, handler: fn(&T, E)) -> Option<Self> {
MainThreadSafe::new((value, handler), |runner, event| {
let lock = runner.read().unwrap();
let (value, handler) = lock.as_ref().unwrap();
handler(value, event);
})
.map(Self)
}
}
impl<T, E> Clone for Channel<T, E> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T, E> Deref for Channel<T, E> {
type Target = ChannelValue<T, E>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@@ -1,115 +0,0 @@
use atomic_waker::AtomicWaker;
use std::future;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError};
use std::sync::{Arc, Mutex};
use std::task::Poll;
// NOTE: This channel doesn't wake up when all senders or receivers are
// dropped. This is acceptable as long as it's only used in `Dispatcher`, which
// has it's own `Drop` behavior.
pub fn channel<T>() -> (AsyncSender<T>, AsyncReceiver<T>) {
let (sender, receiver) = mpsc::channel();
let sender = Arc::new(Mutex::new(sender));
let inner = Arc::new(Inner {
closed: AtomicBool::new(false),
waker: AtomicWaker::new(),
});
let sender = AsyncSender {
sender,
inner: Arc::clone(&inner),
};
let receiver = AsyncReceiver {
receiver: Rc::new(receiver),
inner,
};
(sender, receiver)
}
pub struct AsyncSender<T> {
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
// be accessed on the main thread, as it could block. Additionally we need
// to wrap it in an `Arc` to make it clonable on the main thread without
// having to block.
sender: Arc<Mutex<Sender<T>>>,
inner: Arc<Inner>,
}
impl<T> AsyncSender<T> {
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
self.sender.lock().unwrap().send(event)?;
self.inner.waker.wake();
Ok(())
}
pub fn close(&self) {
self.inner.closed.store(true, Ordering::Relaxed);
self.inner.waker.wake()
}
}
impl<T> Clone for AsyncSender<T> {
fn clone(&self) -> Self {
Self {
sender: Arc::clone(&self.sender),
inner: Arc::clone(&self.inner),
}
}
}
pub struct AsyncReceiver<T> {
receiver: Rc<Receiver<T>>,
inner: Arc<Inner>,
}
impl<T> AsyncReceiver<T> {
pub async fn next(&self) -> Result<T, RecvError> {
future::poll_fn(|cx| match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => {
self.inner.waker.register(cx.waker());
match self.receiver.try_recv() {
Ok(event) => Poll::Ready(Ok(event)),
Err(TryRecvError::Empty) => {
if self.inner.closed.load(Ordering::Relaxed) {
Poll::Ready(Err(RecvError))
} else {
Poll::Pending
}
}
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
}
}
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
})
.await
}
pub fn try_recv(&self) -> Result<Option<T>, RecvError> {
match self.receiver.try_recv() {
Ok(value) => Ok(Some(value)),
Err(TryRecvError::Empty) => Ok(None),
Err(TryRecvError::Disconnected) => Err(RecvError),
}
}
}
impl<T> Clone for AsyncReceiver<T> {
fn clone(&self) -> Self {
Self {
receiver: Rc::clone(&self.receiver),
inner: Arc::clone(&self.inner),
}
}
}
struct Inner {
closed: AtomicBool,
waker: AtomicWaker,
}

View File

@@ -1,113 +0,0 @@
use super::{channel, AsyncReceiver, AsyncSender, Wrapper};
use std::{
cell::Ref,
sync::{Arc, Condvar, Mutex},
};
pub struct Dispatcher<T: 'static>(Wrapper<true, T, AsyncSender<Closure<T>>, Closure<T>>);
struct Closure<T>(Box<dyn FnOnce(&T) + Send>);
impl<T> Dispatcher<T> {
#[track_caller]
pub fn new(value: T) -> Option<(Self, DispatchRunner<T>)> {
let (sender, receiver) = channel::<Closure<T>>();
Wrapper::new(
value,
|value, Closure(closure)| {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
// funny with it here. See `Self::queue()`.
closure(value.borrow().as_ref().unwrap())
},
{
let receiver = receiver.clone();
move |value| async move {
while let Ok(Closure(closure)) = receiver.next().await {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
// funny with it here. See `Self::queue()`.
closure(value.borrow().as_ref().unwrap())
}
}
},
sender,
|sender, closure| {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
// funny with it here. See `Self::queue()`.
sender.send(closure).unwrap()
},
)
.map(|wrapper| (Self(wrapper.clone()), DispatchRunner { wrapper, receiver }))
}
pub fn value(&self) -> Option<Ref<'_, T>> {
self.0.value()
}
pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) {
if let Some(value) = self.0.value() {
f(&value)
} else {
self.0.send(Closure(Box::new(f)))
}
}
pub fn queue<R: Send>(&self, f: impl FnOnce(&T) -> R + Send) -> R {
if let Some(value) = self.0.value() {
f(&value)
} else {
let pair = Arc::new((Mutex::new(None), Condvar::new()));
let closure = Box::new({
let pair = pair.clone();
move |value: &T| {
*pair.0.lock().unwrap() = Some(f(value));
pair.1.notify_one();
}
}) as Box<dyn FnOnce(&T) + Send>;
// SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is
// safe because this function won't return until `f` has finished executing. See
// `Self::new()`.
let closure = Closure(unsafe { std::mem::transmute(closure) });
self.0.send(closure);
let mut started = pair.0.lock().unwrap();
while started.is_none() {
started = pair.1.wait(started).unwrap();
}
started.take().unwrap()
}
}
}
impl<T> Drop for Dispatcher<T> {
fn drop(&mut self) {
self.0.with_sender_data(|sender| sender.close())
}
}
pub struct DispatchRunner<T: 'static> {
wrapper: Wrapper<true, T, AsyncSender<Closure<T>>, Closure<T>>,
receiver: AsyncReceiver<Closure<T>>,
}
impl<T> DispatchRunner<T> {
pub fn run(&self) {
while let Some(Closure(closure)) = self
.receiver
.try_recv()
.expect("should only be closed when `Dispatcher` is dropped")
{
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
// funny with it here. See `Self::queue()`.
closure(
&self
.wrapper
.value()
.expect("don't call this outside the main thread"),
)
}
}
}

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