Compare commits

..

19 Commits

Author SHA1 Message Date
Kirill Chibisov
bbdcb79bbd Release 0.27.5 version 2022-10-26 17:41:45 +03:00
Kirill Chibisov
b8ac784ffc On Wayland, fix invalid offsets being sent in Preedit
Even when the protocol explicitly tells to send proper UTF-8
boundaries for cursor, some IMEs don't do that, so sanity check
them before sending downstream.
2022-10-26 17:41:45 +03:00
Kirill Chibisov
e7037c6ad0 Release 0.27.4 version 2022-10-10 00:37:55 +03:00
Kirill Chibisov
51986bce26 On X11, fix IME crashing during reload
During reload we were picking old styles, but the styles could
change during reload leading to errors during IME building.

Fixes #2510.
2022-10-10 00:37:55 +03:00
Markus Siglreithmaier
429cc58f98 Windows, emit ReceivedCharacter on system keybinds
Currently needed for downstream users relaying on `ReceivedCharacter` for implementing
keybindings.
2022-10-10 00:37:55 +03:00
killian
81303d81a8 On Windows, fixed focus event emission on minimize. 2022-10-10 00:37:55 +03:00
Kirill Chibisov
adfa5bd275 Release 0.27.3 version 2022-09-11 19:59:13 +03:00
Kirill Chibisov
66319c571c On X11 query for XIM styles before creating IME
Fixes #2448.
2022-09-11 19:59:13 +03:00
Kirill Chibisov
f1470d1ab7 Send empty Ime::Preedit before the Ime::Commit
This should help downstream to automatically clear it.
2022-09-11 19:59:13 +03:00
Kirill Chibisov
79dc6bf4ab Remove automatic publish script
This script is confusing and provides no value especially
with release branches and patch fixes.
2022-09-11 19:59:13 +03:00
Kirill Chibisov
ad0520e935 Specify minimum supported version for RWH 0.4
Winit uses raw-window-handle of version 0.4.3,
but only 0.4.0 was specified.
2022-09-11 19:59:13 +03:00
Weng Xuetian
156fa375ea Clear preedit if there is no pending preedit on Wayland
Fixes #2478.
2022-09-11 19:59:13 +03:00
Marijn Suijten
2a2733b2d1 Revert "ci: manually point ANDROID_NDK_ROOT to latest supplied version"
This reverts commit 4895a29e92.

GitHub Actions' runner-images readded this environment variable on my
request [1] as it wasn't strictly related to the deprecated and removed
`ndk-bundle` NDK release.  Back out of the workaround to keep CI scripts
tidy.

[1]: https://github.com/actions/runner-images/issues/5879#issuecomment-1197811704
2022-09-11 19:59:13 +03:00
Mads Marquart
7af1163ee7 iOS: Fix a few instances of UB (#2428)
* Fix iOS 32-bit

* Fix a few invalid message sends on iOS
2022-09-11 19:59:13 +03:00
ajtribick
229dbffa17 Windows: Update handling of system keypresses (#2445)
- Pass WM_SYSKEYDOWN to DefWindowProc
- Avoid intercepting WM_SYSCHAR to allow ALT+Space to work: removes ReceivedCharacter events for alt+keypress
- Intercept WM_MENUCHAR to disable bell sound
2022-09-11 19:59:13 +03:00
daxpedda
a5457b24c2 Document WindowEvent::Moved OS support (#2442) 2022-09-11 19:59:13 +03:00
ajtribick
5645bb1459 Disable default features in simple_logger
This fix CI building due to implicit rust version
bump in examples.
2022-09-11 19:59:13 +03:00
Sludge
77cd021c02 Document WindowEvent::Moved as unsupported on Wayland 2022-09-11 19:59:13 +03:00
Markus Siglreithmaier
2d732069a9 On Windows, improve support for undecorated windows (#2419) 2022-09-11 19:59:13 +03:00
224 changed files with 14149 additions and 27136 deletions

17
.github/CODEOWNERS vendored
View File

@@ -9,18 +9,17 @@
/src/platform_impl/android @msiglreith
# iOS
/src/platform/ios.rs @madsmtm
/src/platform_impl/ios @madsmtm
/src/platform/ios.rs @francesca64
/src/platform_impl/ios @francesca64
# Unix
# Unix in general
/src/platform/unix.rs @kchibisov
/src/platform_impl/linux/mod.rs @kchibisov
# Wayland
/src/platform/wayland.rs @kchibisov
/src/platform_impl/linux/wayland @kchibisov
# X11
/src/platform/x11.rs @kchibisov
/src/platform_impl/linux/x11 @kchibisov
# macOS
@@ -28,13 +27,9 @@
/src/platform_impl/macos @madsmtm
# Web (no maintainer)
/src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda
/src/platform/web.rs
/src/platform_impl/web
# Windows
/src/platform/windows.rs @msiglreith
/src/platform_impl/windows @msiglreith
# Orbital (Redox OS)
/src/platform/orbital.rs @jackpot51
/src/platform_impl/orbital @jackpot51

View File

@@ -9,50 +9,20 @@ jobs:
Check_Formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Check Formatting
run: cargo +stable fmt --all -- --check
cargo-deny:
name: cargo-deny
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- aarch64-apple-ios
- aarch64-linux-android
- i686-pc-windows-gnu
- i686-pc-windows-msvc
- i686-unknown-linux-gnu
- wasm32-unknown-unknown
- x86_64-apple-darwin
- x86_64-apple-ios
- x86_64-pc-windows-gnu
- x86_64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-redox
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform }}
tests:
name: Tests
strategy:
fail-fast: false
matrix:
rust_version: ['1.64.0', stable, nightly]
rust_version: [1.57.0, stable, nightly]
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -63,8 +33,7 @@ jobs:
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { target: aarch64-linux-android, os: ubuntu-latest, options: -p winit, cmd: 'apk --', features: "android-native-activity" }
- { target: x86_64-unknown-redox, os: ubuntu-latest, }
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
@@ -75,6 +44,7 @@ jobs:
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
PKG_CONFIG_ALLOW_CROSS: 1
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
@@ -83,7 +53,7 @@ jobs:
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1
@@ -97,55 +67,44 @@ jobs:
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install Linux dependencies
if: (matrix.platform.os == 'ubuntu-latest')
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libfreetype6-dev:i386 libfontconfig1-dev:i386
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk
- name: Check documentation
shell: bash
run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
- name: Build crate
shell: bash
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
- name: Build tests
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests
shell: bash
if: >
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Lint with clippy
shell: bash
if: (matrix.rust_version == 'stable') && !contains(matrix.platform.options, '--no-default-features')
if: (matrix.rust_version == '1.57.0') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
- name: Build tests with serde enabled
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
if: >
if: (
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES

2
.gitignore vendored
View File

@@ -7,4 +7,4 @@ rls/
*.ts
*.js
#*#
.DS_Store
.DS_Store

View File

@@ -8,186 +8,6 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased
# 0.29.0-beta.0
- On Web, allow event loops to be recreated with `spawn`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- On X11, fix `EventLoopWindowTarget::listen_device_events` effect being reversed.
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Overhaul keyboard input handling.
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
- Change `Event::Key` to contain a `RawKeyEvent`.
- Remove `Event::ReceivedCharacter`. In its place, you should use
`KeyEvent.text` in combination with `WindowEvent::Ime`.
- Replace `VirtualKeyCode` with the `Key` enum.
- Replace `ScanCode` with the `KeyCode` enum.
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
- Add `KeyCode` to refer to keys (roughly) by their physical location.
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
understand.
- Add `Key` to represent the keys after they've been interpreted by the
active (software) keyboard layout.
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
but can appear simultaneously in different spots on the same keyboard
layout.
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
of dead key sequences.
- Add `KeyEventExtModifierSupplement` to expose additional (and less
portable) interpretations of a given key-press.
- Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and
`KeyCode`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- On Orbital, fix `ModifiersChanged` not being sent.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon::Arrow` was removed.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix forward compatibility issues.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable.
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
- **Breaking:** Bump MSRV from `1.60` to `1.64`.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
- On Web: fix position of touch events to be relative to the canvas.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during
a transient activation.
- On Web, fix pointer button events not being processed when a buttons is already pressed.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- On Windows, port to `windows-sys` version 0.48.0.
- On Web, fix pen treated as mouse input.
- On Web, send mouse position on button release as well.
- On Web, fix touch input not gaining or loosing focus.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- On Web, prevent clicks on the canvas to select text.
- On Web, `EventLoopProxy` now implements `Send`.
- On Web, `Window` now implements `Send` and `Sync`.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- On Web, use the correct canvas size when calculating the new size during scale factor change,
instead of using the output bitmap size.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On macOS, fix crash when dropping `Window`.
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to
the canvas size will be reported through `WindowEvent::Resized`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and
`DeviceEvent::Key` support.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events
on Wayland, X11, Windows, macOS and Web.
# 0.28.6
- On macOS, fixed memory leak when getting monitor handle.
- On macOS, fix `Backspace` being emitted when clearing preedit with it.
# 0.28.5
- On macOS, fix `key_up` being ignored when `Ime` is disabled.
# 0.28.4
- On macOS, fix empty marked text blocking regular input.
- On macOS, fix potential panic when getting refresh rate.
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.
# 0.28.3
- Fix macOS memory leaks.
# 0.28.2
- Implement `HasRawDisplayHandle` for `EventLoop`.
- On macOS, set resize increments only for live resizes.
- On Wayland, fix rare crash on DPI change
- Web: Added support for `Window::theme`.
- On Wayland, fix rounding issues when doing resize.
- On macOS, fix wrong focused state on startup.
- On Windows, fix crash on setting taskbar when using Visual Studio debugger.
- On macOS, resize simple fullscreen windows on windowDidChangeScreen events.
# 0.28.1
- On Wayland, fix crash when dropping a window in multi-window setup.
# 0.28.0
- On macOS, fixed `Ime::Commit` persisting for all input after interacting with `Ime`.
- On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`.
- On Windows, fix window size for maximized, undecorated windows.
- On Windows and macOS, add `WindowBuilder::with_active`.
- Add `Window::is_minimized`.
- On X11, fix errors handled during `register_xlib_error_hook` invocation bleeding into winit.
- Add `Window::has_focus`.
- On Windows, fix `Window::set_minimized(false)` not working for windows minimized by `Win + D` hotkey.
- **Breaking:** On Web, touch input no longer fires `WindowEvent::Cursor*`, `WindowEvent::MouseInput`, or `DeviceEvent::MouseMotion` like other platforms, but instead it fires `WindowEvent::Touch`.
- **Breaking:** Removed platform specific `WindowBuilder::with_parent` API in favor of `WindowBuilder::with_parent_window`.
- On Windows, retain `WS_MAXIMIZE` window style when un-minimizing a maximized window.
- On Windows, fix left mouse button release event not being sent after `Window::drag_window`.
- On macOS, run most actions on the main thread, which is strictly more correct, but might make multithreaded applications block slightly more.
- On macOS, fix panic when getting current monitor without any monitor attached.
- On Windows and MacOS, add API to enable/disable window buttons (close, minimize, ...etc).
- On Windows, macOS, X11 and Wayland, add `Window::set_theme`.
- **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`.
- On Windows, revert window background to an empty brush to avoid white flashes when changing scaling.
- **Breaking:** Removed `Window::set_always_on_top` and related APIs in favor of `Window::set_window_level`.
- On Windows, MacOS and X11, add always on bottom APIs.
- On Windows, fix the value in `MouseButton::Other`.
- On macOS, add `WindowExtMacOS::is_document_edited` and `WindowExtMacOS::set_document_edited` APIs.
- **Breaking:** Removed `WindowBuilderExtIOS::with_root_view_class`; instead, you should use `[[view layer] addSublayer: ...]` to add an instance of the desired layer class (e.g. `CAEAGLLayer` or `CAMetalLayer`). See `vulkano-win` or `wgpu` for examples of this.
- On MacOS and Windows, add `Window::set_content_protected`.
- On MacOS, add `EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps`.
- On Windows, fix icons specified on `WindowBuilder` not taking effect for windows created after the first one.
- On Windows and macOS, add `Window::title` to query the current window title.
- On Windows, fix focusing menubar when pressing `Alt`.
- On MacOS, made `accepts_first_mouse` configurable.
- Migrated `WindowBuilderExtUnix::with_resize_increments` to `WindowBuilder`.
- Added `Window::resize_increments`/`Window::set_resize_increments` to update resize increments at runtime for X11/macOS.
- macOS/iOS: Use `objc2` instead of `objc` internally.
- **Breaking:** Bump MSRV from `1.57` to `1.60`.
- **Breaking:** Split the `platform::unix` module into `platform::x11` and `platform::wayland`. The extension types are similarly renamed.
- **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`.
- Removed `parking_lot` dependency.
- **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`.
- **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop.
- On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations.
- On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations.
- On Wayland, if not otherwise specified use upstream automatic CSD theme selection.
- On X11, added `WindowExtX11::with_parent` to create child windows.
- Added support for `WindowBuilder::with_theme` and `Window::theme` to support per-window dark/light/system theme configuration on macos, windows and wayland.
- On macOS, added support for `WindowEvent::ThemeChanged`.
- **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`.
- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`.
- Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels.
- **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444))
- **Breaking:** Removed support for `raw-window-handle` version `0.4`
- On Wayland, `RedrawRequested` not emitted during resize.
- Add a `set_wait_timeout` function to `ControlFlow` to allow waiting for a `Duration`.
- **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface.
- Added Orbital support for Redox OS
- On X11, added `drag_resize_window` method.
- Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS.
- On macOS, fix the mouse buttons other than left/right/middle being reported as middle.
- On Wayland, support fractional scaling via the wp-fractional-scale protocol.
- On web, fix removal of mouse event listeners from the global object upon window distruction.
- Add WindowAttributes getter to WindowBuilder to allow introspection of default values.
- Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only.
# 0.27.5
- On Wayland, fix byte offset in `Ime::Preedit` pointing to invalid bytes.
@@ -198,7 +18,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Windows, fixed focus event emission on minimize.
- On X11, fixed IME crashing during reload.
# 0.27.3
# 0.27.3 (2022-9-10)
- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window.
- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled.
@@ -294,7 +114,6 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
- On MacOS, fix deadlock when calling `set_maximized` from event loop.
# 0.26.1 (2022-01-05)
@@ -396,7 +215,7 @@ And please only add new entries to the top of this list, right below the `# Unre
# 0.23.0 (2020-10-02)
- On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode.
- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode.
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
- On Unix, X11 and Wayland are now optional features (enabled by default)
- On X11, fix deadlock when calling `set_fullscreen_inner`.

View File

@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.64.
- your patch builds with Winit's minimal supported rust version - Rust 1.57.0.
- 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
@@ -48,25 +48,15 @@ The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
## Release process
## Making a new release
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.
If you believe a new release is warranted, you can make a pull-request with:
- An updated version number (remember to change the version everywhere it is used).
- A new section in the changelog (below the `# Unreleased` section).
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.
This gives contributors an opportunity to squeeze in an extra PR or two that they feel is valuable
enough to warrant blocking the release a little.
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`
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
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
Once the PR is merged, a maintainer will create a new tag matching the version name (e.g. `v0.26.1`),
and a CI job will automatically release the new version. Remember that the release date in the
changelog must be kept in check with the actual release date.

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.29.0-beta.0"
version = "0.27.5"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -10,7 +10,7 @@ readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.64.0"
rust-version = "1.57.0"
[package.metadata.docs.rs]
features = ["serde"]
@@ -32,59 +32,48 @@ targets = [
# WebAssembly
"wasm32-unknown-unknown",
]
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "wayland-protocols", "sctk"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/title"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = ["android-activity/native-activity"]
android-game-activity = ["android-activity/game-activity"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
[build-dependencies]
cfg_aliases = "0.1.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.0.0"
log = "0.4"
mint = { version = "0.5.6", optional = true }
instant = { version = "0.1", features = ["wasm-bindgen"] }
once_cell = "1.12"
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0"
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
raw_window_handle_04 = { package = "raw-window-handle", version = "0.4.3" }
bitflags = "1"
mint = { version = "0.5.6", optional = true }
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "2.1.0", default_features = false }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
android-activity = "0.4.0"
ndk = "0.7.0"
ndk-sys = "0.4.0"
ndk-glue = "0.7.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks`
objc = "0.2.7"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22.3"
cocoa = "0.24"
core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2.0"
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
parking_lot = "0.12"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.48"
version = "0.36"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
@@ -112,33 +101,27 @@ features = [
"Win32_UI_WindowsAndMessaging",
]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
libc = "0.2.64"
percent-encoding = { version = "2.0", optional = true }
fnv = { version = "1.0.3", optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.6.0", default_features = false, optional = true }
wayland-client = { version = "0.30.0", optional = true }
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
calloop = "0.10.5"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.4.1", optional = true }
mio = { version = "0.8", features = ["os-ext"], optional = true }
x11-dl = { version = "2.18.5", optional = true }
xkbcommon-dl = "0.4.0"
memmap2 = { version = "0.5.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
parking_lot = { version = "0.12.0", optional = true }
libc = "0.2.64"
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }
redox_syscall = "0.3"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.64"
version = "0.3.22"
features = [
'console',
"AddEventListenerOptions",
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
@@ -147,28 +130,19 @@ features = [
'HtmlElement',
'KeyboardEvent',
'MediaQueryList',
'MediaQueryListEvent',
'MouseEvent',
'Node',
'PageTransitionEvent',
'PointerEvent',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'Window',
'WheelEvent'
]
[target.'cfg(target_family = "wasm")'.dependencies]
atomic-waker = "1"
js-sys = "0.3.64"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-time = "0.2"
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.2"
[workspace]
members = [

View File

@@ -1,14 +1,13 @@
# Winit Scope
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:
be used to create both games and applications. It supports the main graphical platforms:
- Desktop
- Windows 7+ (10+ is tested regularly)
- macOS 10.7+ (10.14+ is tested regularly)
- Windows
- macOS
- Unix
- via X11
- via Wayland
- Redox OS, via Orbital
- Mobile
- iOS
- Android
@@ -130,8 +129,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
@@ -139,7 +136,6 @@ If your PR makes notable changes to Winit's features, please update this section
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* `winit` has a minimum OS requirement of iOS 8
@@ -152,6 +148,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Home indicator visibility
* Status bar visibility
* Deferrring system gestures
* Support for custom `UIView` derived class
* Getting the device idiom
* Getting the preferred video mode
@@ -172,63 +169,62 @@ Legend:
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|Window resize increments |❌ | | |❌ |❌ |❌ |**N/A**|
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**|
|Video mode query |✔️ |✔️ |✔️ |✔️ | |✔️ |**N/A**|
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |❌ |❌ |✔️ |❌ |**N/A**|**N/A**|**N/A** |**N/A** |
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ | |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ | |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ | |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ |
|Keyboard Input ([#812]) | |❌ |❌ |❌ |❌ |❌ |❓ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
@@ -243,5 +239,5 @@ Changes in the API that have been agreed upon but aren't implemented across all
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804
[#812]: https://github.com/rust-windowing/winit/issues/812

110
README.md
View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.29.0-beta.0"
winit = "0.27.5"
```
## [Documentation](https://docs.rs/winit)
@@ -73,6 +73,10 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
Note that windows don't appear on Wayland until you draw/present to them.
`winit` doesn't do drawing, try the examples in [`glutin`] instead.
[`glutin`]: https://github.com/rust-windowing/glutin
#### WebAssembly
To run the web example: `cargo run-wasm --example web`
@@ -95,95 +99,36 @@ book].
#### Android
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
Native Android applications need some form of "glue" crate that is responsible
for defining the main entry point for your Rust application as well as tracking
various life-cycle events and synchronizing with the main JVM thread.
The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
glue crate (prior to `0.28` it used
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
`winit` compatibility table with `ndk-glue`:
The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your
application's main entrypoint. If Cargo resolves multiple versions they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
| 0.25 | `ndk-glue = "0.3"` |
| 0.24 | `ndk-glue = "0.2"` |
The recommended way to avoid a conflict with the glue version is to avoid explicitly
depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*`
| winit | ndk-glue |
| :---: | :------------------: |
| 0.24 | `ndk-glue = "0.2.0"` |
| 0.25 | `ndk-glue = "0.3.0"` |
| 0.26 | `ndk-glue = "0.5.0"` |
| 0.27 | `ndk-glue = "0.7.0"` |
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[lib]
name = "main"
[[example]]
name = "request_redraw_threaded"
crate-type = ["cdylib"]
```
All Android applications are based on an `Activity` subclass and the
`android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag:
| Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
[agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
[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.28", 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:
And add this to the example file to add the native activity glue:
```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);
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
fn main() {
...
}
```
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.28", 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).
And run the application with `cargo apk run --example request_redraw_threaded`
#### MacOS
@@ -194,19 +139,6 @@ and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`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]. You should consider creating your windows
inside `Event::NewEvents(StartCause::Init)`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
[#2051]: https://github.com/rust-windowing/winit/issues/2051
[#2087]: https://github.com/rust-windowing/winit/issues/2087
[#1705]: https://github.com/rust-windowing/winit/issues/1705
#### Redox OS
Redox OS has some functionality not present yet, that will be implemented when
its orbital display server provides it.

View File

@@ -1,24 +0,0 @@
use cfg_aliases::cfg_aliases;
fn main() {
// The script doesn't depend on our code
println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
wasm_platform: { target_family = "wasm" },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform)) },
redox: { target_os = "redox" },
// Native displays.
x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) },
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
orbital_platform: { redox },
}
}

View File

@@ -1,7 +0,0 @@
disallowed-methods = [
{ path = "web_sys::window", reason = "is not available in every context" },
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
]

View File

@@ -1,70 +0,0 @@
# https://embarkstudios.github.io/cargo-deny/
# cargo install cargo-deny
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
targets = [
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown" },
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
{ triple = "x86_64-pc-windows-msvc" },
{ triple = "x86_64-unknown-linux-gnu" },
{ triple = "x86_64-unknown-redox" },
]
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
ignore = []
[bans]
multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "nix" }, # differing version - as of 2023-03-02 whis can be solved with `cargo update && cargo update -p calloop --precise 0.10.2`
{ name = "memoffset"}, # due to different nix versions.
{ name = "memmap2" }, # sctk uses a different version until the next update
{ name = "libloading" }, # x11rb uses a different version until the next update
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 and https://github.com/rustwasm/wasm-bindgen/issues/3390
{ name = "num_enum"}, # See above ^, waiting for release
{ name = "num_enum_derive"},# See above ^, waiting for release
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
]
skip-tree = []
[licenses]
private = { ignore = true }
unlicensed = "deny"
allow-osi-fsf-free = "neither"
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
copyleft = "deny"
allow = [
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
]

View File

@@ -1,11 +0,0 @@
# Image Attribution
These images are used in the documentation of `winit`.
## keyboard_*.svg
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -1,88 +0,0 @@
#[cfg(any(x11_platform, macos_platform, windows_platform))]
#[path = "util/fill.rs"]
mod fill;
#[cfg(any(x11_platform, macos_platform, windows_platform))]
fn main() {
use std::collections::HashMap;
use raw_window_handle::HasRawWindowHandle;
use winit::{
dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
window::{Window, WindowBuilder, WindowId},
};
fn spawn_child_window(
parent: &Window,
event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>,
) {
let parent = parent.raw_window_handle();
let mut builder = WindowBuilder::new()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
builder = unsafe { builder.with_parent_window(Some(parent)) };
let child_window = builder.build(event_loop).unwrap();
let id = child_window.id();
windows.insert(id, child_window);
println!("child window created with id: {id:?}");
}
let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new();
let parent_window = WindowBuilder::new()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32))
.build(&event_loop)
.unwrap();
println!("parent window: {parent_window:?})");
event_loop.run(move |event: Event<'_, ()>, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
windows.clear();
*control_flow = ControlFlow::Exit;
}
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created
// by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the parent window,
// so we also can see this log when we move the cursor arround (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
} => {
spawn_child_window(&parent_window, event_loop, &mut windows);
}
_ => (),
}
} else if let Event::RedrawRequested(wid) = event {
if let Some(window) = windows.get(&wid) {
fill::fill_window(window);
}
}
})
}
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
fn main() {
panic!("This example is supported only on x11, macOS, and Windows.");
}

View File

@@ -1,22 +1,14 @@
#![allow(clippy::single_match)]
use std::thread;
#[cfg(not(wasm_platform))]
use std::time;
#[cfg(wasm_platform)]
use web_time as time;
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Wait,
@@ -48,8 +40,8 @@ fn main() {
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
use winit::event::StartCause;
println!("{event:?}");
use winit::event::{ElementState, StartCause, VirtualKeyCode};
println!("{:?}", event);
match event {
Event::NewEvents(start_cause) => {
wait_cancelled = match start_cause {
@@ -62,33 +54,31 @@ fn main() {
close_requested = true;
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: ElementState::Pressed,
..
},
..
} => match key.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character("1") => {
} => match virtual_code {
VirtualKeyCode::Key1 => {
mode = Mode::Wait;
println!("\nmode: {mode:?}\n");
println!("\nmode: {:?}\n", mode);
}
Key::Character("2") => {
VirtualKeyCode::Key2 => {
mode = Mode::WaitUntil;
println!("\nmode: {mode:?}\n");
println!("\nmode: {:?}\n", mode);
}
Key::Character("3") => {
VirtualKeyCode::Key3 => {
mode = Mode::Poll;
println!("\nmode: {mode:?}\n");
println!("\nmode: {:?}\n", mode);
}
Key::Character("r") => {
VirtualKeyCode::R => {
request_redraw = !request_redraw;
println!("\nrequest_redraw: {request_redraw}\n");
println!("\nrequest_redraw: {}\n", request_redraw);
}
Key::Escape => {
VirtualKeyCode::Escape => {
close_requested = true;
}
_ => (),
@@ -103,15 +93,13 @@ fn main() {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
Event::RedrawRequested(_window_id) => {}
Event::RedrawEventsCleared => {
match mode {
Mode::Wait => control_flow.set_wait(),
Mode::WaitUntil => {
if !wait_cancelled {
control_flow.set_wait_until(time::Instant::now() + WAIT_TIME);
control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
}
}
Mode::Poll => {

View File

@@ -2,14 +2,11 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
window::{CursorIcon, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -26,8 +23,8 @@ fn main() {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
input:
KeyboardInput {
state: ElementState::Pressed,
..
},
@@ -49,9 +46,6 @@ fn main() {
} => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
@@ -60,7 +54,8 @@ fn main() {
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Pointer,
CursorIcon::Hand,
CursorIcon::Arrow,
CursorIcon::Move,
CursorIcon::Text,
CursorIcon::Wait,

View File

@@ -2,15 +2,11 @@
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
window::{CursorGrabMode, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -29,48 +25,45 @@ fn main() {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
..
},
..
} => {
use winit::event::VirtualKeyCode::*;
let result = match key {
Key::Escape => {
Escape => {
control_flow.set_exit();
Ok(())
}
Key::Character(ch) => match ch.to_lowercase().as_str() {
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
"a" => window.set_cursor_grab(CursorGrabMode::None),
"h" => {
window.set_cursor_visible(modifiers.shift_key());
Ok(())
}
_ => Ok(()),
},
G => window.set_cursor_grab(CursorGrabMode::Confined),
L => window.set_cursor_grab(CursorGrabMode::Locked),
A => window.set_cursor_grab(CursorGrabMode::None),
H => {
window.set_cursor_visible(modifiers.shift());
Ok(())
}
_ => Ok(()),
};
if let Err(err) = result {
println!("error: {err}");
println!("error: {}", err);
}
}
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
WindowEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {:?}", delta),
DeviceEvent::Button { button, state } => match state {
ElementState::Pressed => println!("mouse button {button} pressed"),
ElementState::Released => println!("mouse button {button} released"),
ElementState::Pressed => println!("mouse button {} pressed", button),
ElementState::Released => println!("mouse button {} released", button),
},
_ => (),
},
Event::RedrawRequested(_) => fill::fill_window(&window),
_ => (),
}
});

View File

@@ -1,6 +1,6 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use simple_logger::SimpleLogger;
use winit::{
@@ -9,9 +9,6 @@ fn main() {
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy)]
enum CustomEvent {
Timer,
@@ -20,7 +17,7 @@ fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let window = WindowBuilder::new()
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
@@ -42,20 +39,17 @@ fn main() {
control_flow.set_wait();
match event {
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}
#[cfg(wasm_platform)]
#[cfg(target_arch = "wasm32")]
fn main() {
panic!("This example is not supported on web.");
}

View File

@@ -2,15 +2,13 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::EventLoop,
keyboard::Key,
window::{Window, WindowBuilder, WindowId},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -47,27 +45,20 @@ fn main() {
name_windows(entered_id, switched, &window_1, &window_2)
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
input:
KeyboardInput {
state: ElementState::Released,
logical_key: Key::Character(c),
virtual_keycode: Some(VirtualKeyCode::X),
..
},
..
} if c == "x" => {
} => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
_ => (),
},
Event::RedrawRequested(wid) => {
if wid == window_1.id() {
fill::fill_window(&window_1);
} else if wid == window_2.id() {
fill::fill_window(&window_2);
}
}
_ => (),
});
}

View File

@@ -1,17 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::Key;
use winit::window::{Fullscreen, WindowBuilder};
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowExtMacOS;
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -33,14 +26,12 @@ fn main() {
let mut mode_index = 0;
let mut mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {mode}");
println!("Mode: {}", mode);
println!("Keys:");
println!("- Esc\tExit");
println!("- F\tToggle exclusive fullscreen mode");
println!("- B\tToggle borderless mode");
#[cfg(target_os = "macos")]
println!("- C\tToggle simple fullscreen mode");
println!("- S\tNext screen");
println!("- M\tNext mode for this screen");
println!("- D\tToggle window decorations");
@@ -54,81 +45,68 @@ fn main() {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: ElementState::Pressed,
..
},
..
} => match key {
Key::Escape => control_flow.set_exit(),
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character(ch) => match ch.to_lowercase().as_str() {
"f" | "b" if window.fullscreen().is_some() => {
window.set_fullscreen(None);
} => match virtual_code {
VirtualKeyCode::Escape => control_flow.set_exit(),
VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => {
window.set_fullscreen(None);
}
VirtualKeyCode::F => {
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
println!("Setting mode: {:?}", fullscreen);
window.set_fullscreen(fullscreen);
}
VirtualKeyCode::B => {
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
println!("Setting mode: {:?}", fullscreen);
window.set_fullscreen(fullscreen);
}
VirtualKeyCode::S => {
monitor_index += 1;
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
monitor = mon;
} else {
monitor_index = 0;
monitor = elwt.available_monitors().next().expect("no monitor found!");
}
"f" => {
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
println!("Setting mode: {fullscreen:?}");
window.set_fullscreen(fullscreen);
}
"b" => {
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
println!("Setting mode: {fullscreen:?}");
window.set_fullscreen(fullscreen);
}
#[cfg(target_os = "macos")]
"c" => {
window.set_simple_fullscreen(!window.simple_fullscreen());
}
"s" => {
monitor_index += 1;
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
monitor = mon;
} else {
monitor_index = 0;
monitor =
elwt.available_monitors().next().expect("no monitor found!");
}
println!("Monitor: {:?}", monitor.name());
println!("Monitor: {:?}", monitor.name());
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {}", mode);
}
VirtualKeyCode::M => {
mode_index += 1;
if let Some(m) = monitor.video_modes().nth(mode_index) {
mode = m;
} else {
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {mode}");
}
"m" => {
mode_index += 1;
if let Some(m) = monitor.video_modes().nth(mode_index) {
mode = m;
} else {
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
}
println!("Mode: {mode}");
}
"d" => {
decorations = !decorations;
window.set_decorations(decorations);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
"z" => {
minimized = !minimized;
window.set_minimized(minimized);
}
_ => (),
},
println!("Mode: {}", mode);
}
VirtualKeyCode::D => {
decorations = !decorations;
window.set_decorations(decorations);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
VirtualKeyCode::Z => {
minimized = !minimized;
window.set_minimized(minimized);
}
_ => (),
},
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => {}
}
});

View File

@@ -2,20 +2,16 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{Event, KeyboardInput, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
let _window = WindowBuilder::new()
.with_title("Your faithful window")
.build(&event_loop)
.unwrap();
@@ -23,6 +19,10 @@ fn main() {
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
use winit::event::{
ElementState::Released,
VirtualKeyCode::{N, Y},
};
control_flow.set_wait();
match event {
@@ -46,18 +46,16 @@ fn main() {
// the Y key.
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
..
},
..
} => {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
match key.as_ref() {
Key::Character("y") => {
match virtual_code {
Y => {
if close_requested {
// This is where you'll want to do any cleanup you need.
println!("Buh-bye!");
@@ -70,7 +68,7 @@ fn main() {
control_flow.set_exit();
}
}
Key::Character("n") => {
N => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
@@ -82,9 +80,6 @@ fn main() {
_ => (),
}
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -3,16 +3,12 @@
use log::LevelFilter;
use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, Ime, WindowEvent},
dpi::PhysicalPosition,
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, KeyCode},
window::{ImePurpose, WindowBuilder},
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new()
.with_level(LevelFilter::Trace)
@@ -22,7 +18,6 @@ fn main() {
println!("IME position will system default");
println!("Click to set IME position to cursor's");
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
println!("Press F3 to cycle through IME purposes.");
let event_loop = EventLoop::new();
@@ -31,7 +26,6 @@ fn main() {
.build(&event_loop)
.unwrap();
let mut ime_purpose = ImePurpose::Normal;
let mut ime_allowed = true;
window.set_ime_allowed(ime_allowed);
@@ -66,42 +60,38 @@ fn main() {
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
window.set_ime_position(ime_pos);
}
}
Event::WindowEvent {
event: WindowEvent::Ime(event),
..
} => {
println!("{event:?}");
println!("{:?}", event);
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
window.set_ime_position(ime_pos);
}
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { event, .. },
event: WindowEvent::ReceivedCharacter(ch),
..
} => {
println!("key: {event:?}");
println!("ch: {:?}", ch);
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => {
println!("key: {:?}", input);
if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 {
if input.state == ElementState::Pressed
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
{
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME allowed: {ime_allowed}\n");
println!("\nIME: {}\n", ime_allowed);
}
if event.state == ElementState::Pressed && event.logical_key == Key::F3 {
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,
_ => ImePurpose::Normal,
};
window.set_ime_purpose(ime_purpose);
println!("\nIME purpose: {ime_purpose:?}\n");
}
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}

View File

@@ -1,65 +0,0 @@
#![allow(clippy::single_match)]
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, ModifiersState},
// WARNING: This is not available on all platforms (for example on the web).
platform::modifier_supplement::KeyEventExtModifierSupplement,
window::WindowBuilder,
};
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
fn main() {
println!("This example is not supported on this platform");
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
fn main() {
#[path = "util/fill.rs"]
mod fill;
simple_logger::SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(400.0, 200.0))
.build(&event_loop)
.unwrap();
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
WindowEvent::KeyboardInput { event, .. } => {
if event.state == ElementState::Pressed && !event.repeat {
match event.key_without_modifiers().as_ref() {
Key::Character("1") => {
if modifiers.shift_key() {
println!("Shift + 1 | logical_key: {:?}", event.logical_key);
} else {
println!("1");
}
}
_ => (),
}
}
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
});
}

View File

@@ -1,8 +1,6 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::monitor::MonitorHandle;
use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
@@ -10,49 +8,6 @@ fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
if let Some(mon) = window.primary_monitor() {
print_info("Primary output", mon);
}
for mon in window.available_monitors() {
if Some(&mon) == window.primary_monitor().as_ref() {
continue;
}
println!();
print_info("Output", mon);
}
}
fn print_info(intro: &str, monitor: MonitorHandle) {
if let Some(name) = monitor.name() {
println!("{intro}: {name}");
} else {
println!("{intro}: [no name]");
}
let PhysicalSize { width, height } = monitor.size();
print!(" Current mode: {width}x{height}");
if let Some(m_hz) = monitor.refresh_rate_millihertz() {
println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
} else {
println!();
}
let PhysicalPosition { x, y } = monitor.position();
println!(" Position: {x},{y}");
println!(" Scale factor: {}", monitor.scale_factor());
println!(" Available modes (width x height x bit-depth):");
for mode in monitor.video_modes() {
let PhysicalSize { width, height } = mode.size();
let bits = mode.bit_depth();
let m_hz = mode.refresh_rate_millihertz();
println!(
" {width}x{height}x{bits} @ {}.{} Hz",
m_hz / 1000,
m_hz % 1000
);
}
dbg!(window.available_monitors().collect::<Vec<_>>());
dbg!(window.primary_monitor());
}

View File

@@ -7,9 +7,6 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -42,7 +39,7 @@ In other words, the deltas indicate the direction in which to move the content (
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
println!("mouse wheel Line Delta: ({x},{y})");
println!("mouse wheel Line Delta: ({},{})", x, y);
let pixels_per_line = 120.0;
let mut pos = window.outer_position().unwrap();
pos.x += (x * pixels_per_line) as i32;
@@ -59,9 +56,6 @@ In other words, the deltas indicate the direction in which to move the content (
},
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -1,16 +1,15 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
@@ -30,7 +29,6 @@ fn main() {
let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx);
let mut modifiers = ModifiersState::default();
thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
@@ -53,116 +51,105 @@ fn main() {
);
}
}
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
#[allow(deprecated)]
WindowEvent::KeyboardInput {
event:
KeyEvent {
input:
KeyboardInput {
state: ElementState::Released,
logical_key: key,
virtual_keycode: Some(key),
modifiers,
..
},
..
} => {
use Key::{ArrowLeft, ArrowRight};
window.set_title(&format!("{key:?}"));
let state = !modifiers.shift_key();
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift();
use VirtualKeyCode::*;
match key {
A => window.set_always_on_top(state),
C => window.set_cursor_icon(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),
D => window.set_decorations(!state),
// Cycle through video modes
Key::ArrowRight | Key::ArrowLeft => {
Right | Left => {
video_mode_id = match key {
ArrowLeft => video_mode_id.saturating_sub(1),
ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1),
Left => video_mode_id.saturating_sub(1),
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!("Picking video mode: {}", video_modes[video_mode_id]);
}
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character(ch) => match ch.to_lowercase().as_str() {
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
"3" => window.set_window_level(WindowLevel::Normal),
"c" => window.set_cursor_icon(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),
"d" => window.set_decorations(!state),
"f" => window.set_fullscreen(match (state, modifiers.alt_key()) {
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => Some(Fullscreen::Exclusive(
video_modes[video_mode_id].clone(),
)),
(false, _) => None,
}),
"l" if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked)
{
println!("error: {err}");
}
F => window.set_fullscreen(match (state, modifiers.alt()) {
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => {
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
}
"g" if state => {
if let Err(err) =
window.set_cursor_grab(CursorGrabMode::Confined)
{
println!("error: {err}");
}
(false, _) => None,
}),
L if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
println!("error: {}", err);
}
"g" | "l" if !state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
println!("error: {err}");
}
}
G if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
println!("error: {}", err);
}
"h" => window.set_cursor_visible(!state),
"i" => {
println!("Info:");
println!("-> outer_position : {:?}", window.outer_position());
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
}
G | L if !state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
println!("error: {}", err);
}
"l" => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
false => None,
}),
"m" => window.set_maximized(state),
"p" => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * sign;
position
}),
"q" => window.request_redraw(),
"r" => window.set_resizable(state),
"s" => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
"w" => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.set_cursor_position(Position::Physical(
PhysicalPosition::new(
size.width as i32 / 2,
size.height as i32 / 2,
),
))
.unwrap()
}
}
H => window.set_cursor_visible(!state),
I => {
println!("Info:");
println!("-> outer_position : {:?}", window.outer_position());
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
}
L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
false => None,
}),
M => window.set_maximized(state),
P => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * sign;
position
}),
Q => window.request_redraw(),
R => window.set_resizable(state),
S => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
W => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.set_cursor_position(Position::Physical(
PhysicalPosition::new(
size.width as i32 / 2,
size.height as i32 / 2,
),
))
.unwrap()
}
"z" => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
window.set_visible(true);
}
_ => (),
},
}
Z => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
window.set_visible(true);
}
_ => (),
}
}
@@ -181,10 +168,10 @@ fn main() {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput {
event:
KeyEvent {
input:
KeyboardInput {
state: ElementState::Released,
logical_key: Key::Escape,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
@@ -204,7 +191,7 @@ fn main() {
})
}
#[cfg(wasm_platform)]
#[cfg(target_arch = "wasm32")]
fn main() {
panic!("Example not supported on Wasm");
}

View File

@@ -4,15 +4,11 @@ use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -33,7 +29,7 @@ fn main() {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
println!("Window {:?} has received the signal to close", window_id);
// This drops the window, causing it to close.
windows.remove(&window_id);
@@ -43,15 +39,15 @@ fn main() {
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
input:
KeyboardInput {
state: ElementState::Pressed,
logical_key: Key::Character(c),
virtual_keycode: Some(VirtualKeyCode::N),
..
},
is_synthetic: false,
..
} if matches!(c.as_ref(), "n" | "N") => {
} => {
let window = Window::new(event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
@@ -59,11 +55,6 @@ fn main() {
_ => (),
}
}
Event::RedrawRequested(window_id) => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
})

View File

@@ -7,9 +7,6 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -20,7 +17,7 @@ fn main() {
.unwrap();
event_loop.run(move |event, _, control_flow| {
println!("{event:?}");
println!("{:?}", event);
control_flow.set_wait();
@@ -37,7 +34,6 @@ fn main() {
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}

View File

@@ -1,8 +1,8 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::{sync::Arc, thread, time};
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
@@ -11,30 +11,21 @@ fn main() {
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = {
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
Arc::new(window)
};
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
thread::spawn({
let window = window.clone();
move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
}
thread::spawn(move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
});
event_loop.run(move |event, _, control_flow| {
println!("{event:?}");
println!("{:?}", event);
control_flow.set_wait();
@@ -45,14 +36,13 @@ fn main() {
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
});
}
#[cfg(wasm_platform)]
#[cfg(target_arch = "wasm32")]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

View File

@@ -3,15 +3,11 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
keyboard::KeyCode,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -34,23 +30,20 @@ fn main() {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: KeyCode::Space,
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Released,
..
},
..
} => {
resizable = !resizable;
println!("Resizable: {resizable}");
println!("Resizable: {}", resizable);
window.set_resizable(resizable);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
});

View File

@@ -1,79 +0,0 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::Key,
window::{Theme, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_theme(Some(Theme::Dark))
.build(&event_loop)
.unwrap();
println!("Initial theme: {:?}", window.theme());
println!("debugging keys:");
println!(" (A) Automatic theme");
println!(" (L) Light theme");
println!(" (D) Dark theme");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::ThemeChanged(theme),
window_id,
..
} if window_id == window.id() => {
println!("Theme is changed: {theme:?}")
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
},
..
} => match key.as_ref() {
Key::Character("A" | "a") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(None);
}
Key::Character("L" | "l") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Light));
}
Key::Character("D" | "d") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Dark));
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
});
}

View File

@@ -1,10 +1,7 @@
#![allow(clippy::single_match)]
use instant::Instant;
use std::time::Duration;
#[cfg(not(wasm_platform))]
use std::time::Instant;
#[cfg(wasm_platform)]
use web_time::Instant;
use simple_logger::SimpleLogger;
use winit::{
@@ -13,14 +10,11 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
@@ -28,7 +22,7 @@ fn main() {
let timer_length = Duration::new(1, 0);
event_loop.run(move |event, _, control_flow| {
println!("{event:?}");
println!("{:?}", event);
match event {
Event::NewEvents(StartCause::Init) => {
@@ -42,9 +36,6 @@ fn main() {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -1,51 +0,0 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Touchpad gestures")
.build(&event_loop)
.unwrap();
println!("Only supported on macOS at the moment.");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::TouchpadMagnify { delta, .. } => {
if delta > 0.0 {
println!("Zoomed in {delta}");
} else {
println!("Zoomed out {delta}");
}
}
WindowEvent::SmartMagnify { .. } => {
println!("Smart zoom");
}
WindowEvent::TouchpadRotate { delta, .. } => {
if delta > 0.0 {
println!("Rotated counterclockwise {delta}");
} else {
println!("Rotated clockwise {delta}");
}
}
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
}

View File

@@ -7,9 +7,6 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -24,16 +21,13 @@ fn main() {
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
println!("{event:?}");
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -1,86 +0,0 @@
//! Fill the window buffer with a solid color.
//!
//! Launching a window without drawing to it has unpredictable results varying from platform to
//! platform. In order to have well-defined examples, this module provides an easy way to
//! fill the window buffer with a solid color.
//!
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
use winit::window::Window;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub(super) fn fill_window(window: &Window) {
use softbuffer::{Context, Surface};
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use winit::window::WindowId;
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: Context,
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface>,
}
impl GraphicsContext {
fn new(w: &Window) -> Self {
Self {
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
surfaces: HashMap::new(),
}
}
fn surface(&mut self, w: &Window) -> &mut Surface {
self.surfaces.entry(w.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, w) }
.expect("Failed to create a softbuffer surface")
})
}
}
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
}
GC.with(|gc| {
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc
.get_or_insert_with(|| GraphicsContext::new(window))
.surface(window);
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xFF181818;
let size = window.inner_size();
surface
.resize(
NonZeroU32::new(size.width).expect("Width must be greater than zero"),
NonZeroU32::new(size.height).expect("Height must be greater than zero"),
)
.expect("Failed to resize the softbuffer surface");
let mut buffer = surface
.buffer_mut()
.expect("Failed to get the softbuffer buffer");
buffer.fill(DARK_GRAY);
buffer
.present()
.expect("Failed to present the softbuffer buffer");
})
}
#[cfg(any(target_os = "android", target_os = "ios"))]
pub(super) fn fill_window(_window: &Window) {
// No-op on mobile platforms.
}

View File

@@ -17,6 +17,6 @@ fn main() {
println!("Listing available video modes:");
for mode in monitor.video_modes() {
println!("{mode}");
println!("{}", mode);
}
}

View File

@@ -1,10 +1,9 @@
#![allow(clippy::disallowed_methods, clippy::single_match)]
#![allow(clippy::single_match)]
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{Event, WindowEvent},
event_loop::EventLoop,
keyboard::KeyCode,
window::{Fullscreen, WindowBuilder},
window::WindowBuilder,
};
pub fn main() {
@@ -15,13 +14,13 @@ pub fn main() {
.build(&event_loop)
.unwrap();
#[cfg(wasm_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window);
#[cfg(target_arch = "wasm32")]
let log_list = wasm::create_log_list(&window);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
#[cfg(wasm_platform)]
#[cfg(target_arch = "wasm32")]
wasm::log_event(&log_list, &event);
match event {
@@ -32,31 +31,12 @@ pub fn main() {
Event::MainEventsCleared => {
window.request_redraw();
}
Event::WindowEvent {
window_id,
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: KeyCode::KeyF,
state: ElementState::Released,
..
},
..
},
} if window_id == window.id() => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
_ => (),
}
});
}
#[cfg(wasm_platform)]
#[cfg(target_arch = "wasm32")]
mod wasm {
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
@@ -69,20 +49,17 @@ mod wasm {
super::main();
}
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
pub fn create_log_list(window: &Window) -> web_sys::Element {
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas().unwrap();
let canvas = window.canvas();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell where the canvas is for debugging purposes.
canvas
.style()
.set_property("background-color", "crimson")
.unwrap();
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
canvas.style().set_css_text("background-color: crimson;");
body.append_child(&canvas).unwrap();
let log_header = document.create_element("h2").unwrap();
@@ -104,7 +81,7 @@ mod wasm {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let log = document.create_element("li").unwrap();
log.set_text_content(Some(&format!("{event:?}")));
log.set_text_content(Some(&format!("{:?}", event)));
log_list
.insert_before(&log, log_list.first_child().as_ref())
.unwrap();

View File

@@ -1,105 +0,0 @@
#![allow(clippy::disallowed_methods)]
pub fn main() {
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
}
#[cfg(wasm_platform)]
mod wasm {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::HtmlCanvasElement;
use winit::{
dpi::PhysicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
};
const EXPLANATION: &str = "
This example draws a circle in the middle of a 4/1 aspect ratio canvas which acts as a useful demonstration of winit's resize handling on web.
Even when the browser window is resized or aspect-ratio of the canvas changed the circle should always:
* Fill the entire width or height of the canvas (whichever is smaller) without exceeding it.
* Be perfectly round
* Not be blurry or pixelated (there is no antialiasing so you may still see jagged edges depending on the DPI of your monitor)
Currently winit does not handle resizes on web so the circle is rendered incorrectly.
This example demonstrates the desired future functionality which will possibly be provided by https://github.com/rust-windowing/winit/pull/2074
";
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
// When running in a non-wasm environment this would set the window size to 100x100.
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
.with_inner_size(PhysicalSize::new(100, 100))
.build(&event_loop)
.unwrap();
let canvas = create_canvas(&window);
// Render once with the size info we currently have
render_circle(&canvas, window.inner_size());
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::Resized(resize),
window_id,
} if window_id == window.id() => {
render_circle(&canvas, resize);
}
_ => (),
}
});
}
pub fn create_canvas(window: &Window) -> HtmlCanvasElement {
use winit::platform::web::WindowExtWebSys;
let web_window = web_sys::window().unwrap();
let document = web_window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
let canvas = window.canvas().unwrap();
canvas
.style()
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
body.append_child(&canvas).unwrap();
let explanation = document.create_element("pre").unwrap();
explanation.set_text_content(Some(EXPLANATION));
body.append_child(&explanation).unwrap();
canvas
}
pub fn render_circle(canvas: &HtmlCanvasElement, size: PhysicalSize<u32>) {
log::info!("rendering circle with canvas size: {:?}", size);
let context = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
context.begin_path();
context
.arc(
size.width as f64 / 2.0,
size.height as f64 / 2.0,
size.width.min(size.height) as f64 / 2.0,
0.0,
std::f64::consts::PI * 2.0,
)
.unwrap();
context.fill();
}
}

View File

@@ -7,9 +7,6 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -22,7 +19,7 @@ fn main() {
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
println!("{event:?}");
println!("{:?}", event);
match event {
Event::WindowEvent {
@@ -32,9 +29,6 @@ fn main() {
Event::MainEventsCleared => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -1,75 +0,0 @@
#![allow(clippy::single_match)]
// This example is used by developers to test various window functions.
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::Key,
window::{WindowBuilder, WindowButtons},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(300.0, 300.0))
.build(&event_loop)
.unwrap();
eprintln!("Window Button keys:");
eprintln!(" (F) Toggle close button");
eprintln!(" (G) Toggle maximize button");
eprintln!(" (H) Toggle minimize button");
event_loop.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
},
..
} => match key.as_ref() {
Key::Character("F" | "f") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
}
Key::Character("G" | "g") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
}
Key::Character("H" | "h") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}

View File

@@ -5,15 +5,11 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::{Key, KeyCode},
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{DeviceEventFilter, EventLoop},
window::{Fullscreen, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -36,31 +32,28 @@ fn main() {
let mut minimized = false;
let mut visible = true;
event_loop.listen_device_events(DeviceEvents::Always);
event_loop.set_device_event_filter(DeviceEventFilter::Never);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
// This used to use the virtual key, but the new API
// only provides the `physical_key` (`Code`).
Event::DeviceEvent {
event:
DeviceEvent::Key(RawKeyEvent {
physical_key,
state: ElementState::Released,
DeviceEvent::Key(KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
}),
..
} => match physical_key {
KeyCode::KeyM => {
} => match key {
VirtualKeyCode::M => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
window.focus_window();
}
}
KeyCode::KeyV => {
VirtualKeyCode::V => {
if !visible {
visible = !visible;
window.set_visible(visible);
@@ -71,19 +64,17 @@ fn main() {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(key_str),
input:
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
},
..
},
..
} => match key_str.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
"e" => {
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
@@ -98,7 +89,7 @@ fn main() {
eprintln!("no video modes available");
}
}
"f" => {
VirtualKeyCode::F => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
@@ -106,25 +97,25 @@ fn main() {
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
"p" => {
VirtualKeyCode::P => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
"m" => {
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
}
"q" => {
VirtualKeyCode::Q => {
control_flow.set_exit();
}
"v" => {
VirtualKeyCode::V => {
visible = !visible;
window.set_visible(visible);
}
"x" => {
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
@@ -134,9 +125,6 @@ fn main() {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -1,146 +0,0 @@
//! Demonstrates capability to create in-app draggable regions for client-side decoration support.
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::Key,
window::{CursorIcon, ResizeDirection, WindowBuilder},
};
const BORDER: f64 = 8.0;
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
.with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0))
.with_decorations(false)
.build(&event_loop)
.unwrap();
let mut border = false;
let mut cursor_location = None;
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Press 'B' to toggle borderless")
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CursorMoved { position, .. } => {
if !window.is_decorated() {
let new_location =
cursor_resize_direction(window.inner_size(), position, BORDER);
if new_location != cursor_location {
cursor_location = new_location;
window.set_cursor_icon(cursor_direction_icon(cursor_location))
}
}
}
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
if let Some(dir) = cursor_location {
let _res = window.drag_resize_window(dir);
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
logical_key: Key::Character(c),
..
},
..
} if matches!(c.as_ref(), "B" | "b") => {
border = !border;
window.set_decorations(border);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
});
}
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {
match resize_direction {
Some(resize_direction) => match resize_direction {
ResizeDirection::East => CursorIcon::EResize,
ResizeDirection::North => CursorIcon::NResize,
ResizeDirection::NorthEast => CursorIcon::NeResize,
ResizeDirection::NorthWest => CursorIcon::NwResize,
ResizeDirection::South => CursorIcon::SResize,
ResizeDirection::SouthEast => CursorIcon::SeResize,
ResizeDirection::SouthWest => CursorIcon::SwResize,
ResizeDirection::West => CursorIcon::WResize,
},
None => CursorIcon::Default,
}
}
fn cursor_resize_direction(
win_size: winit::dpi::PhysicalSize<u32>,
position: winit::dpi::PhysicalPosition<f64>,
border_size: f64,
) -> Option<ResizeDirection> {
enum XDirection {
West,
East,
Default,
}
enum YDirection {
North,
South,
Default,
}
let xdir = if position.x < border_size {
XDirection::West
} else if position.x > (win_size.width as f64 - border_size) {
XDirection::East
} else {
XDirection::Default
};
let ydir = if position.y < border_size {
YDirection::North
} else if position.y > (win_size.height as f64 - border_size) {
YDirection::South
} else {
YDirection::Default
};
Some(match xdir {
XDirection::West => match ydir {
YDirection::North => ResizeDirection::NorthWest,
YDirection::South => ResizeDirection::SouthWest,
YDirection::Default => ResizeDirection::West,
},
XDirection::East => match ydir {
YDirection::North => ResizeDirection::NorthEast,
YDirection::South => ResizeDirection::SouthEast,
YDirection::Default => ResizeDirection::East,
},
XDirection::Default => match ydir {
YDirection::North => ResizeDirection::North,
YDirection::South => ResizeDirection::South,
YDirection::Default => return None,
},
})
}

View File

@@ -9,9 +9,6 @@ use winit::{
window::{Icon, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
@@ -45,8 +42,6 @@ fn main() {
}
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
}

View File

@@ -1,75 +0,0 @@
#![allow(clippy::single_match)]
#[cfg(target_os = "macos")]
use winit::platform::macos::{OptionAsAlt, WindowExtMacOS};
#[cfg(target_os = "macos")]
use winit::{
event::ElementState,
event::{Event, MouseButton, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[cfg(target_os = "macos")]
#[path = "util/fill.rs"]
mod fill;
/// Prints the keyboard events characters received when option_is_alt is true versus false.
/// A left mouse click will toggle option_is_alt.
#[cfg(target_os = "macos")]
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
window.set_ime_allowed(true);
let mut option_as_alt = window.option_as_alt();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
option_as_alt = match option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
window.set_option_as_alt(option_as_alt);
}
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
_ => (),
},
Event::MainEventsCleared => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("This example is only supported on MacOS");
}

View File

@@ -1,64 +0,0 @@
use log::debug;
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(128.0, 128.0))
.with_resize_increments(LogicalSize::new(25.0, 25.0))
.build(&event_loop)
.unwrap();
let mut has_increments = true;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Space,
state: ElementState::Released,
..
},
..
},
window_id,
} if window_id == window.id() => {
has_increments = !has_increments;
let new_increments = match window.resize_increments() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}

View File

@@ -2,12 +2,14 @@
// Limit this example to only compatible platforms.
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
android_platform,
orbital_platform,
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "android",
))]
fn main() {
use std::{thread::sleep, time::Duration};
@@ -19,14 +21,10 @@ fn main() {
platform::run_return::EventLoopExtRunReturn,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
let mut event_loop = EventLoop::new();
SimpleLogger::new().init().unwrap();
let window = WindowBuilder::new()
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
@@ -39,7 +37,7 @@ fn main() {
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{event:?}");
println!("{:?}", event);
}
match event {
@@ -52,9 +50,6 @@ fn main() {
Event::MainEventsCleared => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
@@ -65,7 +60,7 @@ fn main() {
}
}
#[cfg(any(ios_platform, wasm_platform))]
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
fn main() {
println!("This platform doesn't support run_return.");
}

View File

@@ -6,4 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cargo-run-wasm = "0.2.0"
cargo-run-wasm = "0.1.0"

View File

@@ -1,3 +1,3 @@
fn main() {
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
cargo_run_wasm::run_wasm();
}

View File

@@ -167,7 +167,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> {
pub x: P,
@@ -246,7 +246,7 @@ impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
}
/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
@@ -325,7 +325,7 @@ impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
}
/// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> {
pub width: P,
@@ -407,7 +407,7 @@ impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> {
pub width: P,
@@ -519,8 +519,18 @@ impl Size {
max.into().to_physical::<f64>(scale_factor),
);
let width = input.width.clamp(min.width, max.width);
let height = input.height.clamp(min.height, max.height);
let clamp = |input: f64, min: f64, max: f64| {
if input < min {
min
} else if input > max {
max
} else {
input
}
};
let width = clamp(input.width, min.width, max.width);
let height = clamp(input.height, min.height, max.height);
PhysicalSize::new(width, height).into()
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,14 +9,11 @@
//! handle events.
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{error, fmt};
use instant::Instant;
use once_cell::sync::OnceCell;
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
#[cfg(not(wasm_platform))]
use std::time::{Duration, Instant};
#[cfg(wasm_platform)]
use web_time::{Duration, Instant};
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
@@ -69,8 +66,6 @@ impl EventLoopBuilder<()> {
}
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl<T> EventLoopBuilder<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
@@ -102,24 +97,14 @@ impl<T> EventLoopBuilder<T> {
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
/// will fall back on X11. If this variable is set with any other value, winit will panic.
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`.
///
/// [`platform`]: crate::platform
#[cfg_attr(
android,
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)]
#[cfg_attr(
not(android),
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> EventLoop<T> {
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
if EVENT_LOOP_CREATED.set(()).is_err() {
panic!("Creating EventLoop multiple times is not supported.");
}
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
EventLoop {
@@ -127,11 +112,6 @@ impl<T> EventLoopBuilder<T> {
_marker: PhantomData,
}
}
#[cfg(wasm_platform)]
pub(crate) fn allow_event_loop_recreation() {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
}
impl<T> fmt::Debug for EventLoop<T> {
@@ -165,11 +145,16 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
///
/// ## Platform-specific
///
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
/// example when the scaling of the page has changed. This should be treated as an implementation
/// detail which should not be relied on.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
@@ -179,7 +164,6 @@ pub enum ControlFlow {
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
@@ -227,20 +211,6 @@ impl ControlFlow {
*self = Self::WaitUntil(instant);
}
/// Sets this to wait until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn set_wait_timeout(&mut self, timeout: Duration) {
match Instant::now().checked_add(timeout) {
Some(instant) => self.set_wait_until(instant),
None => self.set_wait(),
}
}
/// Sets this to [`ExitWithCode`]`(code)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
@@ -316,13 +286,6 @@ impl<T> EventLoop<T> {
}
}
unsafe impl<T> HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.event_loop.window_target().p.raw_display_handle()
}
}
impl<T> Deref for EventLoop<T> {
type Target = EventLoopWindowTarget<T>;
fn deref(&self) -> &EventLoopWindowTarget<T> {
@@ -334,7 +297,6 @@ impl<T> EventLoopWindowTarget<T> {
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
#[allow(clippy::useless_conversion)] // false positive on some platforms
self.p
.available_monitors()
.into_iter()
@@ -350,25 +312,30 @@ impl<T> EventLoopWindowTarget<T> {
/// **Wayland:** Always returns `None`.
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.p
.primary_monitor()
.map(|inner| MonitorHandle { inner })
self.p.primary_monitor()
}
/// Change if or when [`DeviceEvent`]s are captured.
/// Change [`DeviceEvent`] filter mode.
///
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
/// this at runtime to explicitly capture them again.
/// this filter at runtime to explicitly capture them again.
///
/// ## Platform-specific
///
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
/// - **Wayland / macOS / iOS / Android / Web:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
#[cfg(any(x11_platform, wasm_platform, wayland_platform, windows))]
self.p.listen_device_events(_allowed);
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "windows"
))]
self.p.set_device_event_filter(_filter);
}
}
@@ -426,14 +393,19 @@ impl<T> fmt::Display for EventLoopClosed<T> {
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum DeviceEvents {
/// Report device events regardless of window focus.
/// Filter controlling the propagation of device events.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum DeviceEventFilter {
/// Always filter out device events.
Always,
/// Only capture device events while the window is focused.
#[default]
WhenFocused,
/// Never capture device events.
/// Filter out device events while the window is not focused.
Unfocused,
/// Report all device events regardless of window focus.
Never,
}
impl Default for DeviceEventFilter {
fn default() -> Self {
Self::Unfocused
}
}

View File

@@ -34,7 +34,8 @@ impl fmt::Display for BadIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadIcon::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.",
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
byte_count,
),
BadIcon::DimensionsVsPixelCount {
width,
@@ -42,9 +43,10 @@ impl fmt::Display for BadIcon {
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:?}.",
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
width, height, pixel_count, width_x_height,
),
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"),
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -135,8 +135,6 @@
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::missing_safety_doc)]
#[allow(unused_imports)]
@@ -147,6 +145,9 @@ extern crate log;
extern crate serde;
#[macro_use]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
pub mod dpi;
#[macro_use]
@@ -154,7 +155,6 @@ pub mod error;
pub mod event;
pub mod event_loop;
mod icon;
pub mod keyboard;
pub mod monitor;
mod platform_impl;
pub mod window;

View File

@@ -32,9 +32,12 @@ impl PartialOrd for VideoMode {
impl Ord for VideoMode {
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
// to `u32` there
let size: (u32, u32) = self.size().into();
let other_size: (u32, u32) = other.size().into();
self.monitor().cmp(&other.monitor()).then(
self.size()
.cmp(&other.size())
size.cmp(&other_size)
.then(
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
@@ -58,7 +61,7 @@ impl VideoMode {
///
/// ## Platform-specific
///
/// - **Wayland / Orbital:** Always returns 32.
/// - **Wayland:** Always returns 32.
/// - **iOS:** Always returns 32.
#[inline]
pub fn bit_depth(&self) -> u16 {
@@ -75,9 +78,7 @@ impl VideoMode {
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
MonitorHandle {
inner: self.video_mode.monitor(),
}
self.video_mode.monitor()
}
}
@@ -140,9 +141,6 @@ impl MonitorHandle {
/// The monitor refresh rate used by the system.
///
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
/// the window is on is removed.
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// enter fullscreen should be used instead.
#[inline]
@@ -171,8 +169,6 @@ impl MonitorHandle {
/// - **Web:** Always returns an empty iterator
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner
.video_modes()
.map(|video_mode| VideoMode { video_mode })
self.inner.video_modes()
}
}

View File

@@ -1,9 +1,11 @@
#![cfg(any(target_os = "android"))]
use crate::{
event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget},
event_loop::{EventLoop, EventLoopWindowTarget},
window::{Window, WindowBuilder},
};
use android_activity::{AndroidApp, ConfigurationRef, Rect};
use ndk::configuration::Configuration;
use ndk_glue::Rect;
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
@@ -17,7 +19,7 @@ pub trait EventLoopWindowTargetExtAndroid {}
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;
fn config(&self) -> ConfigurationRef;
fn config(&self) -> Configuration;
}
impl WindowExtAndroid for Window {
@@ -25,7 +27,7 @@ impl WindowExtAndroid for Window {
self.window.content_rect()
}
fn config(&self) -> ConfigurationRef {
fn config(&self) -> Configuration {
self.window.config()
}
}
@@ -36,53 +38,3 @@ impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
pub trait WindowBuilderExtAndroid {}
impl WindowBuilderExtAndroid for WindowBuilder {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
///
/// Default is to let the operating system handle the volume keys
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
/// applications can rely on the Winit crate to resolve the required version of
/// `android_activity` and avoid any chance of a conflict between Winit and the
/// application crate.
///
/// Unlike most libraries there can only be a single implementation
/// of the `android_activity` glue crate linked with an application because
/// it is responsible for the application's `android_main()` entry point.
///
/// Since Winit depends on a specific version of `android_activity` the simplest
/// way to avoid creating a conflict is for applications to avoid explicitly
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os="android")]
/// use winit::platform::android::activity::AndroidApp;
/// ```
pub mod activity {
pub use android_activity::*;
}

View File

@@ -1,6 +1,6 @@
use std::os::raw::c_void;
#![cfg(target_os = "ios")]
use objc2::rc::Id;
use std::os::raw::c_void;
use crate::{
event_loop::EventLoop,
@@ -99,17 +99,17 @@ pub trait WindowExtIOS {
impl WindowExtIOS for Window {
#[inline]
fn ui_window(&self) -> *mut c_void {
self.window.ui_window()
self.window.ui_window() as _
}
#[inline]
fn ui_view_controller(&self) -> *mut c_void {
self.window.ui_view_controller()
self.window.ui_view_controller() as _
}
#[inline]
fn ui_view(&self) -> *mut c_void {
self.window.ui_view()
self.window.ui_view() as _
}
#[inline]
@@ -141,6 +141,13 @@ impl WindowExtIOS for Window {
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
pub trait WindowBuilderExtIOS {
/// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
///
/// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc).
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
@@ -190,6 +197,12 @@ pub trait WindowBuilderExtIOS {
}
impl WindowBuilderExtIOS for WindowBuilder {
#[inline]
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
self
}
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
self.platform_specific.scale_factor = Some(scale_factor);
@@ -241,22 +254,19 @@ pub trait MonitorHandleExtIOS {
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
Id::as_ptr(self.inner.ui_screen()) as *mut c_void
self.inner.ui_screen() as _
}
#[inline]
fn preferred_video_mode(&self) -> VideoMode {
VideoMode {
video_mode: self.inner.preferred_video_mode(),
}
self.inner.preferred_video_mode()
}
}
/// Valid orientations for a particular [`Window`].
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, Debug)]
pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone
#[default]
LandscapeAndPortrait,
Landscape,
@@ -265,10 +275,17 @@ pub enum ValidOrientations {
Portrait,
}
impl Default for ValidOrientations {
#[inline]
fn default() -> ValidOrientations {
ValidOrientations::LandscapeAndPortrait
}
}
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Idiom {
Unspecified,
@@ -287,14 +304,14 @@ bitflags! {
/// The [edges] of a screen.
///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Default)]
pub struct ScreenEdge: u8 {
const NONE = 0;
const TOP = 1 << 0;
const LEFT = 1 << 1;
const BOTTOM = 1 << 2;
const RIGHT = 1 << 3;
const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits()
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits
| ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits;
}
}

View File

@@ -1,8 +1,9 @@
#![cfg(target_os = "macos")]
use std::os::raw::c_void;
use objc2::rc::Id;
use crate::{
dpi::LogicalSize,
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
@@ -37,36 +38,6 @@ pub trait WindowExtMacOS {
/// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool);
/// Get the window's edit state.
///
/// # Examples
///
/// ```ignore
/// WindowEvent::CloseRequested => {
/// if window.is_document_edited() {
/// // Show the user a save pop-up or similar
/// } else {
/// // Close the window
/// drop(window);
/// }
/// }
/// ```
fn is_document_edited(&self) -> bool;
/// Put the window in a state which indicates a file save is required.
fn set_document_edited(&self, edited: bool);
/// Set option as alt behavior as described in [`OptionAsAlt`].
///
/// This will ignore diacritical marks and accent characters from
/// being processed as received characters. Instead, the input
/// device's raw character will be placed in event queues with the
/// Alt modifier set.
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt;
}
impl WindowExtMacOS for Window {
@@ -99,42 +70,25 @@ impl WindowExtMacOS for Window {
fn set_has_shadow(&self, has_shadow: bool) {
self.window.set_has_shadow(has_shadow)
}
#[inline]
fn is_document_edited(&self) -> bool {
self.window.is_document_edited()
}
#[inline]
fn set_document_edited(&self, edited: bool) {
self.window.set_document_edited(edited)
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window.set_option_as_alt(option_as_alt)
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
self.window.option_as_alt()
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`.
#[default]
Regular,
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
Accessory,
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
Prohibited,
}
impl Default for ActivationPolicy {
fn default() -> Self {
ActivationPolicy::Regular
}
}
/// Additional methods on [`WindowBuilder`] that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
@@ -157,14 +111,10 @@ pub trait WindowBuilderExtMacOS {
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
/// Window accepts click-through mouse events.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
/// Set how the <kbd>Option</kbd> keys are interpreted.
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
}
impl WindowBuilderExtMacOS for WindowBuilder {
@@ -207,6 +157,12 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments);
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
self.platform_specific.disallow_hidpi = disallow_hidpi;
@@ -218,18 +174,6 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self.platform_specific.has_shadow = has_shadow;
self
}
#[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> WindowBuilder {
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
self.platform_specific.option_as_alt = option_as_alt;
self
}
}
pub trait EventLoopBuilderExtMacOS {
@@ -276,12 +220,6 @@ pub trait EventLoopBuilderExtMacOS {
/// # }
/// ```
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
/// Used to prevent the application from automatically activating when launched if
/// another application is already active.
///
/// The default behavior is to ignore other applications and activate when launched.
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
@@ -296,12 +234,6 @@ impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
self.platform_specific.default_menu = enable;
self
}
#[inline]
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
self.platform_specific.activate_ignoring_other_apps = ignore;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
@@ -319,7 +251,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
}
fn ns_screen(&self) -> Option<*mut c_void> {
self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
self.inner.ns_screen().map(|s| s as *mut c_void)
}
}
@@ -340,23 +272,3 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
self.p.hide_other_applications()
}
}
/// Option as alt behavior.
///
/// The default is `None`.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OptionAsAlt {
/// The left `Option` key is treated as `Alt`.
OnlyLeft,
/// The right `Option` key is treated as `Alt`.
OnlyRight,
/// Both `Option` keys are treated as `Alt`.
Both,
/// No special handling is applied for `Option` key.
#[default]
None,
}

View File

@@ -15,31 +15,11 @@
//!
//! However only the module corresponding to the platform you're compiling to will be available.
#[cfg(android_platform)]
pub mod android;
#[cfg(ios_platform)]
pub mod ios;
#[cfg(macos_platform)]
pub mod macos;
#[cfg(orbital_platform)]
pub mod orbital;
#[cfg(wayland_platform)]
pub mod wayland;
#[cfg(wasm_platform)]
pub mod unix;
pub mod web;
#[cfg(windows_platform)]
pub mod windows;
#[cfg(x11_platform)]
pub mod x11;
pub mod modifier_supplement;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
orbital_platform
))]
pub mod run_return;
pub mod scancode;

View File

@@ -1,24 +0,0 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::Key;
/// Additional methods for the `KeyEvent` which cannot be implemented on all
/// platforms.
pub trait KeyEventExtModifierSupplement {
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
fn text_with_all_modifiers(&self) -> Option<&str>;
/// This value ignores all modifiers including,
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
/// and <kbd>Ctrl</kbd>. In most cases this means that the
/// unicode character in the resulting string is lowercase.
///
/// This is useful for key-bindings / shortcut key combinations.
///
/// In case `logical_key` reports `Dead`, this will still report the
/// key as `Character` according to the current keyboard layout. This value
/// cannot be `Dead`.
fn key_without_modifiers(&self) -> Key;
}

View File

@@ -1 +0,0 @@
// There are no Orbital specific traits yet.

View File

@@ -1,3 +1,14 @@
#![cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},

View File

@@ -1,30 +0,0 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::KeyCode;
// TODO: Describe what this value contains for each platform
/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific
/// scancode.
///
/// [`KeyCode`]: crate::keyboard::KeyCode
pub trait KeyCodeExtScancode {
/// The raw value of the platform-specific physical key identifier.
///
/// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise.
///
/// ## Platform-specific
/// - **Windows:** A 16bit extended scancode
/// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8.
fn to_scancode(self) -> Option<u32>;
/// Constructs a `KeyCode` from a platform-specific physical key identifier.
///
/// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back
/// using `to_scancode` might not yield the original value.
///
/// ## Platform-specific
/// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract
/// `8` to get the value you wanted.
fn from_scancode(scancode: u32) -> KeyCode;
}

464
src/platform/unix.rs Normal file
View File

@@ -0,0 +1,464 @@
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use std::os::raw;
#[cfg(feature = "x11")]
use std::{ptr, sync::Arc};
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
#[cfg(feature = "x11")]
use crate::dpi::Size;
#[cfg(feature = "x11")]
use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS};
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
// TODO: stupid hack so that glutin can do its work
#[doc(hidden)]
#[cfg(feature = "x11")]
pub use crate::platform_impl::x11;
#[cfg(feature = "x11")]
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
#[cfg(feature = "wayland")]
pub use crate::window::Theme;
/// The first argument in the provided hook will be the pointer to `XDisplay`
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
/// indicator whether the error was handled by the callback.
///
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
#[cfg(feature = "x11")]
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors comming from X11 when using xlib
/// in external crates, like glutin for GLX access. Trying to handle errors by speculating with
/// `XSetErrorHandler` is [`unsafe`].
///
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
#[inline]
#[cfg(feature = "x11")]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
XLIB_ERROR_HOOKS.lock().push(hook);
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool;
/// True if the [`EventLoopWindowTarget`] uses X11.
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
#[inline]
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.p {
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.display().get_display_ptr() as *mut _)
}
#[cfg(feature = "x11")]
_ => None,
}
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
pub trait EventLoopBuilderExtUnix {
/// Force using X11.
#[cfg(feature = "x11")]
fn with_x11(&mut self) -> &mut Self;
/// Force using Wayland.
#[cfg(feature = "wayland")]
fn with_wayland(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtUnix for EventLoopBuilder<T> {
#[inline]
#[cfg(feature = "x11")]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::X);
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::Wayland);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to Unix.
pub trait WindowExtUnix {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void>;
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int>;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Updates [`Theme`] of window decorations.
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, config: Theme);
/// Check if the window is ready for drawing
///
/// It is a remnant of a previous implementation detail for the
/// wayland backend, and is no longer relevant.
///
/// Always return `true`.
#[deprecated]
fn is_ready(&self) -> bool;
}
impl WindowExtUnix for Window {
#[inline]
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
#[cfg(feature = "x11")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
#[cfg(feature = "x11")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, theme: Theme) {
#[allow(clippy::single_match)]
match self.window {
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
#[cfg(feature = "x11")]
_ => (),
}
}
#[inline]
fn is_ready(&self) -> bool {
true
}
}
/// Additional methods on [`WindowBuilder`] that are specific to Unix.
pub trait WindowBuilderExtUnix {
#[cfg(feature = "x11")]
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
#[cfg(feature = "x11")]
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with the given `general` and `instance` names.
///
/// On Wayland, the `general` name sets an application ID, which should match the `.desktop`
/// file destributed with your program. The `instance` is a `no-op`.
///
/// On X11, the `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with certain decoration [`Theme`]
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(self, theme: Theme) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
/// Build window with base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
}
impl WindowBuilderExtUnix for WindowBuilder {
#[inline]
#[cfg(feature = "x11")]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(mut self, theme: Theme) -> Self {
self.platform_specific.csd_theme = Some(theme);
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Linux.
pub trait MonitorHandleExtUnix {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtUnix for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -1,146 +0,0 @@
use std::os::raw;
use sctk::reexports::client::Proxy;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
pub use crate::window::Theme;
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland.
pub trait EventLoopWindowTargetExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
fn is_wayland(&self) -> bool;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.connection.display().id().as_ptr() as *mut _)
}
#[cfg(x11_platform)]
_ => None,
}
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
pub trait EventLoopBuilderExtWayland {
/// Force using Wayland.
fn with_wayland(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::Wayland);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to Wayland.
pub trait WindowExtWayland {
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtWayland for Window {
#[inline]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
}
/// Additional methods on [`WindowBuilder`] that are specific to Wayland.
pub trait WindowBuilderExtWayland {
/// Build window with the given name.
///
/// The `general` name sets an application ID, which should match the `.desktop`
/// file destributed with your program. The `instance` is a `no-op`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
}
impl WindowBuilderExtWayland for WindowBuilder {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Wayland.
pub trait MonitorHandleExtWayland {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtWayland for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -1,3 +1,5 @@
#![cfg(target_arch = "wasm32")]
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
@@ -12,8 +14,7 @@ use crate::window::WindowBuilder;
use web_sys::HtmlCanvasElement;
pub trait WindowExtWebSys {
/// Only returns the canvas if called from inside the window.
fn canvas(&self) -> Option<HtmlCanvasElement>;
fn canvas(&self) -> HtmlCanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
@@ -27,9 +28,6 @@ pub trait WindowBuilderExtWebSys {
///
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
/// the default behavior of scrolling the page.
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
fn with_prevent_default(self, prevent_default: bool) -> Self;
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
@@ -66,11 +64,6 @@ pub trait EventLoopExtWebSys {
///
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
/// satisfy its `!` return type.
///
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
/// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application.
fn spawn<F>(self, event_handler: F)
where
F: 'static

View File

@@ -1,14 +1,14 @@
#![cfg(target_os = "windows")]
use std::{ffi::c_void, path::Path};
use crate::{
dpi::PhysicalSize,
event::{DeviceId, KeyEvent},
event::DeviceId,
event_loop::EventLoopBuilder,
keyboard::Key,
monitor::MonitorHandle,
platform::modifier_supplement::KeyEventExtModifierSupplement,
platform_impl::WinIcon,
window::{BadIcon, Icon, Window, WindowBuilder},
platform_impl::{Parent, WinIcon},
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
};
/// Window Handle type used by Win32 API
@@ -138,6 +138,9 @@ pub trait WindowExtWindows {
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Returns the current window theme.
fn theme(&self) -> Theme;
/// Whether to show or hide the window icon in the taskbar.
fn set_skip_taskbar(&self, skip: bool);
@@ -168,6 +171,11 @@ impl WindowExtWindows for Window {
self.window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn theme(&self) -> Theme {
self.window.theme()
}
#[inline]
fn set_skip_taskbar(&self, skip: bool) {
self.window.set_skip_taskbar(skip)
@@ -181,8 +189,14 @@ impl WindowExtWindows for Window {
/// Additional methods on `WindowBuilder` that are specific to Windows.
pub trait WindowBuilderExtWindows {
/// Sets a parent to the window to be created.
///
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box.
///
@@ -220,6 +234,9 @@ pub trait WindowBuilderExtWindows {
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
/// Forces a theme or uses the system settings if `None` was provided.
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
@@ -231,9 +248,15 @@ pub trait WindowBuilderExtWindows {
}
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Parent::ChildOf(parent);
self
}
#[inline]
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.owner = Some(parent);
self.platform_specific.parent = Parent::OwnedBy(parent);
self
}
@@ -261,6 +284,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
self
}
#[inline]
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
self.platform_specific.preferred_theme = theme;
self
}
#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
self.platform_specific.skip_taskbar = skip;
@@ -346,18 +375,3 @@ impl IconExtWindows for Icon {
Ok(Icon { inner: win_icon })
}
}
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifers
.as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}

View File

@@ -1,233 +0,0 @@
use std::os::raw;
use std::ptr;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::dpi::Size;
use crate::platform_impl::{
x11::ffi::XVisualInfo, ApplicationName, Backend, Window as LinuxWindow, XLIB_ERROR_HOOKS,
};
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// The first argument in the provided hook will be the pointer to `XDisplay`
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
/// indicator whether the error was handled by the callback.
///
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors comming from X11
/// when using xlib in external crates, like glutin for GLX access. Trying to
/// handle errors by speculating with `XSetErrorHandler` is [`unsafe`].
///
/// **Be aware that your hook is always invoked and returning `true` from it will
/// prevent `winit` from getting the error itself. It's wise to always return
/// `false` if you're not initiated the `Sync`.**
///
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
#[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to X11.
pub trait EventLoopWindowTargetExtX11 {
/// True if the [`EventLoopWindowTarget`] uses X11.
fn is_x11(&self) -> bool;
}
impl<T> EventLoopWindowTargetExtX11 for EventLoopWindowTarget<T> {
#[inline]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to X11.
pub trait EventLoopBuilderExtX11 {
/// Force using X11.
fn with_x11(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::X);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to X11.
pub trait WindowExtX11 {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xlib_display(&self) -> Option<*mut raw::c_void>;
fn xlib_screen_id(&self) -> Option<raw::c_int>;
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtX11 for Window {
#[inline]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(wayland_platform)]
_ => None,
}
}
}
/// Additional methods on [`WindowBuilder`] that are specific to X11.
pub trait WindowBuilderExtX11 {
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with the given `general` and `instance` names.
///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::x11::WindowBuilderExtX11;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
}
impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
self
}
#[inline]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
}
/// Additional methods on `MonitorHandle` that are specific to X11.
pub trait MonitorHandleExtX11 {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtX11 for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -1,521 +0,0 @@
use android_activity::input::Keycode;
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
match keycode {
Keycode::A => KeyCode::KeyA,
Keycode::B => KeyCode::KeyB,
Keycode::C => KeyCode::KeyC,
Keycode::D => KeyCode::KeyD,
Keycode::E => KeyCode::KeyE,
Keycode::F => KeyCode::KeyF,
Keycode::G => KeyCode::KeyG,
Keycode::H => KeyCode::KeyH,
Keycode::I => KeyCode::KeyI,
Keycode::J => KeyCode::KeyJ,
Keycode::K => KeyCode::KeyK,
Keycode::L => KeyCode::KeyL,
Keycode::M => KeyCode::KeyM,
Keycode::N => KeyCode::KeyN,
Keycode::O => KeyCode::KeyO,
Keycode::P => KeyCode::KeyP,
Keycode::Q => KeyCode::KeyQ,
Keycode::R => KeyCode::KeyR,
Keycode::S => KeyCode::KeyS,
Keycode::T => KeyCode::KeyT,
Keycode::U => KeyCode::KeyU,
Keycode::V => KeyCode::KeyV,
Keycode::W => KeyCode::KeyW,
Keycode::X => KeyCode::KeyX,
Keycode::Y => KeyCode::KeyY,
Keycode::Z => KeyCode::KeyZ,
Keycode::Keycode0 => KeyCode::Digit0,
Keycode::Keycode1 => KeyCode::Digit1,
Keycode::Keycode2 => KeyCode::Digit2,
Keycode::Keycode3 => KeyCode::Digit3,
Keycode::Keycode4 => KeyCode::Digit4,
Keycode::Keycode5 => KeyCode::Digit5,
Keycode::Keycode6 => KeyCode::Digit6,
Keycode::Keycode7 => KeyCode::Digit7,
Keycode::Keycode8 => KeyCode::Digit8,
Keycode::Keycode9 => KeyCode::Digit9,
Keycode::Numpad0 => KeyCode::Numpad0,
Keycode::Numpad1 => KeyCode::Numpad1,
Keycode::Numpad2 => KeyCode::Numpad2,
Keycode::Numpad3 => KeyCode::Numpad3,
Keycode::Numpad4 => KeyCode::Numpad4,
Keycode::Numpad5 => KeyCode::Numpad5,
Keycode::Numpad6 => KeyCode::Numpad6,
Keycode::Numpad7 => KeyCode::Numpad7,
Keycode::Numpad8 => KeyCode::Numpad8,
Keycode::Numpad9 => KeyCode::Numpad9,
Keycode::NumpadAdd => KeyCode::NumpadAdd,
Keycode::NumpadSubtract => KeyCode::NumpadSubtract,
Keycode::NumpadMultiply => KeyCode::NumpadMultiply,
Keycode::NumpadDivide => KeyCode::NumpadDivide,
Keycode::NumpadEnter => KeyCode::NumpadEnter,
Keycode::NumpadEquals => KeyCode::NumpadEqual,
Keycode::NumpadComma => KeyCode::NumpadComma,
Keycode::NumpadDot => KeyCode::NumpadDecimal,
Keycode::NumLock => KeyCode::NumLock,
Keycode::DpadLeft => KeyCode::ArrowLeft,
Keycode::DpadRight => KeyCode::ArrowRight,
Keycode::DpadUp => KeyCode::ArrowUp,
Keycode::DpadDown => KeyCode::ArrowDown,
Keycode::F1 => KeyCode::F1,
Keycode::F2 => KeyCode::F2,
Keycode::F3 => KeyCode::F3,
Keycode::F4 => KeyCode::F4,
Keycode::F5 => KeyCode::F5,
Keycode::F6 => KeyCode::F6,
Keycode::F7 => KeyCode::F7,
Keycode::F8 => KeyCode::F8,
Keycode::F9 => KeyCode::F9,
Keycode::F10 => KeyCode::F10,
Keycode::F11 => KeyCode::F11,
Keycode::F12 => KeyCode::F12,
Keycode::Space => KeyCode::Space,
Keycode::Escape => KeyCode::Escape,
Keycode::Enter => KeyCode::Enter, // not on the Numpad
Keycode::Tab => KeyCode::Tab,
Keycode::PageUp => KeyCode::PageUp,
Keycode::PageDown => KeyCode::PageDown,
Keycode::MoveHome => KeyCode::Home,
Keycode::MoveEnd => KeyCode::End,
Keycode::Insert => KeyCode::Insert,
Keycode::Del => KeyCode::Backspace, // Backspace (above Enter)
Keycode::ForwardDel => KeyCode::Delete, // Delete (below Insert)
Keycode::Copy => KeyCode::Copy,
Keycode::Paste => KeyCode::Paste,
Keycode::Cut => KeyCode::Cut,
Keycode::VolumeUp => KeyCode::AudioVolumeUp,
Keycode::VolumeDown => KeyCode::AudioVolumeDown,
Keycode::VolumeMute => KeyCode::AudioVolumeMute,
//Keycode::Mute => None, // Microphone mute
Keycode::MediaPlayPause => KeyCode::MediaPlayPause,
Keycode::MediaStop => KeyCode::MediaStop,
Keycode::MediaNext => KeyCode::MediaTrackNext,
Keycode::MediaPrevious => KeyCode::MediaTrackPrevious,
Keycode::Plus => KeyCode::Equal,
Keycode::Minus => KeyCode::Minus,
// Winit doesn't differentiate both '+' and '=', considering they are usually
// on the same physical key
Keycode::Equals => KeyCode::Equal,
Keycode::Semicolon => KeyCode::Semicolon,
Keycode::Slash => KeyCode::Slash,
Keycode::Backslash => KeyCode::Backslash,
Keycode::Comma => KeyCode::Comma,
Keycode::Period => KeyCode::Period,
Keycode::Apostrophe => KeyCode::Quote,
Keycode::Grave => KeyCode::Backquote,
// Winit doesn't expose a SysRq code, so map to PrintScreen since it's
// usually the same physical key
Keycode::Sysrq => KeyCode::PrintScreen,
// These are usually the same (Pause/Break)
Keycode::Break => KeyCode::Pause,
// These are exactly the same
Keycode::ScrollLock => KeyCode::ScrollLock,
Keycode::Yen => KeyCode::IntlYen,
Keycode::Kana => KeyCode::Lang1,
Keycode::KatakanaHiragana => KeyCode::KanaMode,
Keycode::CtrlLeft => KeyCode::ControlLeft,
Keycode::CtrlRight => KeyCode::ControlRight,
Keycode::ShiftLeft => KeyCode::ShiftLeft,
Keycode::ShiftRight => KeyCode::ShiftRight,
Keycode::AltLeft => KeyCode::AltLeft,
Keycode::AltRight => KeyCode::AltRight,
Keycode::MetaLeft => KeyCode::SuperLeft,
Keycode::MetaRight => KeyCode::SuperRight,
Keycode::LeftBracket => KeyCode::BracketLeft,
Keycode::RightBracket => KeyCode::BracketRight,
Keycode::Power => KeyCode::Power,
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp,
keycode => KeyCode::Unidentified(NativeKeyCode::Android(keycode.into())),
}
}
// TODO: We need to expose getUnicodeChar via android-activity instead of having
// a fixed mapping from key codes
pub fn to_logical(keycode: Keycode, native: NativeKey) -> Key {
use android_activity::input::Keycode::*;
match keycode {
Unknown => Key::Unidentified(native),
// Can be added on demand
SoftLeft => Key::Unidentified(native),
SoftRight => Key::Unidentified(native),
// Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::BrowserHome,
Back => Key::BrowserBack,
Call => Key::Call,
Endcall => Key::EndCall,
//-------------------------------------------------------------------------------
// Reporting unidentified, because the specific character is layout dependent.
// (I'm not sure though)
Keycode0 => Key::Character("0".into()),
Keycode1 => Key::Character("1".into()),
Keycode2 => Key::Character("2".into()),
Keycode3 => Key::Character("3".into()),
Keycode4 => Key::Character("4".into()),
Keycode5 => Key::Character("5".into()),
Keycode6 => Key::Character("6".into()),
Keycode7 => Key::Character("7".into()),
Keycode8 => Key::Character("8".into()),
Keycode9 => Key::Character("9".into()),
Star => Key::Character("*".into()),
Pound => Key::Character("#".into()),
A => Key::Character("a".into()),
B => Key::Character("b".into()),
C => Key::Character("c".into()),
D => Key::Character("d".into()),
E => Key::Character("e".into()),
F => Key::Character("f".into()),
G => Key::Character("g".into()),
H => Key::Character("h".into()),
I => Key::Character("i".into()),
J => Key::Character("j".into()),
K => Key::Character("k".into()),
L => Key::Character("l".into()),
M => Key::Character("m".into()),
N => Key::Character("n".into()),
O => Key::Character("o".into()),
P => Key::Character("p".into()),
Q => Key::Character("q".into()),
R => Key::Character("r".into()),
S => Key::Character("s".into()),
T => Key::Character("t".into()),
U => Key::Character("u".into()),
V => Key::Character("v".into()),
W => Key::Character("w".into()),
X => Key::Character("x".into()),
Y => Key::Character("y".into()),
Z => Key::Character("z".into()),
Comma => Key::Character(",".into()),
Period => Key::Character(".".into()),
Grave => Key::Character("`".into()),
Minus => Key::Character("-".into()),
Equals => Key::Character("=".into()),
LeftBracket => Key::Character("[".into()),
RightBracket => Key::Character("]".into()),
Backslash => Key::Character("\\".into()),
Semicolon => Key::Character(";".into()),
Apostrophe => Key::Character("'".into()),
Slash => Key::Character("/".into()),
At => Key::Character("@".into()),
Plus => Key::Character("+".into()),
//-------------------------------------------------------------------------------
DpadUp => Key::ArrowUp,
DpadDown => Key::ArrowDown,
DpadLeft => Key::ArrowLeft,
DpadRight => Key::ArrowRight,
DpadCenter => Key::Enter,
VolumeUp => Key::AudioVolumeUp,
VolumeDown => Key::AudioVolumeDown,
Power => Key::Power,
Camera => Key::Camera,
Clear => Key::Clear,
AltLeft => Key::Alt,
AltRight => Key::Alt,
ShiftLeft => Key::Shift,
ShiftRight => Key::Shift,
Tab => Key::Tab,
Space => Key::Space,
Sym => Key::Symbol,
Explorer => Key::LaunchWebBrowser,
Envelope => Key::LaunchMail,
Enter => Key::Enter,
Del => Key::Backspace,
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => Key::Alt,
Headsethook => Key::HeadsetHook,
Focus => Key::CameraFocus,
Menu => Key::Unidentified(native),
Notification => Key::Notification,
Search => Key::BrowserSearch,
MediaPlayPause => Key::MediaPlayPause,
MediaStop => Key::MediaStop,
MediaNext => Key::MediaTrackNext,
MediaPrevious => Key::MediaTrackPrevious,
MediaRewind => Key::MediaRewind,
MediaFastForward => Key::MediaFastForward,
Mute => Key::MicrophoneVolumeMute,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
Pictsymbols => Key::Unidentified(native),
SwitchCharset => Key::Unidentified(native),
// -----------------------------------------------------------------
// Gamepad events should be exposed through a separate API, not
// keyboard events
ButtonA => Key::Unidentified(native),
ButtonB => Key::Unidentified(native),
ButtonC => Key::Unidentified(native),
ButtonX => Key::Unidentified(native),
ButtonY => Key::Unidentified(native),
ButtonZ => Key::Unidentified(native),
ButtonL1 => Key::Unidentified(native),
ButtonR1 => Key::Unidentified(native),
ButtonL2 => Key::Unidentified(native),
ButtonR2 => Key::Unidentified(native),
ButtonThumbl => Key::Unidentified(native),
ButtonThumbr => Key::Unidentified(native),
ButtonStart => Key::Unidentified(native),
ButtonSelect => Key::Unidentified(native),
ButtonMode => Key::Unidentified(native),
// -----------------------------------------------------------------
Escape => Key::Escape,
ForwardDel => Key::Delete,
CtrlLeft => Key::Control,
CtrlRight => Key::Control,
CapsLock => Key::CapsLock,
ScrollLock => Key::ScrollLock,
MetaLeft => Key::Super,
MetaRight => Key::Super,
Function => Key::Fn,
Sysrq => Key::PrintScreen,
Break => Key::Pause,
MoveHome => Key::Home,
MoveEnd => Key::End,
Insert => Key::Insert,
Forward => Key::BrowserForward,
MediaPlay => Key::MediaPlay,
MediaPause => Key::MediaPause,
MediaClose => Key::MediaClose,
MediaEject => Key::Eject,
MediaRecord => Key::MediaRecord,
F1 => Key::F1,
F2 => Key::F2,
F3 => Key::F3,
F4 => Key::F4,
F5 => Key::F5,
F6 => Key::F6,
F7 => Key::F7,
F8 => Key::F8,
F9 => Key::F9,
F10 => Key::F10,
F11 => Key::F11,
F12 => Key::F12,
NumLock => Key::NumLock,
Numpad0 => Key::Character("0".into()),
Numpad1 => Key::Character("1".into()),
Numpad2 => Key::Character("2".into()),
Numpad3 => Key::Character("3".into()),
Numpad4 => Key::Character("4".into()),
Numpad5 => Key::Character("5".into()),
Numpad6 => Key::Character("6".into()),
Numpad7 => Key::Character("7".into()),
Numpad8 => Key::Character("8".into()),
Numpad9 => Key::Character("9".into()),
NumpadDivide => Key::Character("/".into()),
NumpadMultiply => Key::Character("*".into()),
NumpadSubtract => Key::Character("-".into()),
NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Enter,
NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()),
VolumeMute => Key::AudioVolumeMute,
Info => Key::Info,
ChannelUp => Key::ChannelUp,
ChannelDown => Key::ChannelDown,
ZoomIn => Key::ZoomIn,
ZoomOut => Key::ZoomOut,
Tv => Key::TV,
Window => Key::Unidentified(native),
Guide => Key::Guide,
Dvr => Key::DVR,
Bookmark => Key::BrowserFavorites,
Captions => Key::ClosedCaptionToggle,
Settings => Key::Settings,
TvPower => Key::TVPower,
TvInput => Key::TVInput,
StbPower => Key::STBPower,
StbInput => Key::STBInput,
AvrPower => Key::AVRPower,
AvrInput => Key::AVRInput,
ProgRed => Key::ColorF0Red,
ProgGreen => Key::ColorF1Green,
ProgYellow => Key::ColorF2Yellow,
ProgBlue => Key::ColorF3Blue,
AppSwitch => Key::AppSwitch,
Button1 => Key::Unidentified(native),
Button2 => Key::Unidentified(native),
Button3 => Key::Unidentified(native),
Button4 => Key::Unidentified(native),
Button5 => Key::Unidentified(native),
Button6 => Key::Unidentified(native),
Button7 => Key::Unidentified(native),
Button8 => Key::Unidentified(native),
Button9 => Key::Unidentified(native),
Button10 => Key::Unidentified(native),
Button11 => Key::Unidentified(native),
Button12 => Key::Unidentified(native),
Button13 => Key::Unidentified(native),
Button14 => Key::Unidentified(native),
Button15 => Key::Unidentified(native),
Button16 => Key::Unidentified(native),
LanguageSwitch => Key::GroupNext,
MannerMode => Key::MannerMode,
Keycode3dMode => Key::TV3DMode,
Contacts => Key::LaunchContacts,
Calendar => Key::LaunchCalendar,
Music => Key::LaunchMusicPlayer,
Calculator => Key::LaunchApplication2,
ZenkakuHankaku => Key::ZenkakuHankaku,
Eisu => Key::Eisu,
Muhenkan => Key::NonConvert,
Henkan => Key::Convert,
KatakanaHiragana => Key::HiraganaKatakana,
Yen => Key::Unidentified(native),
Ro => Key::Unidentified(native),
Kana => Key::KanjiMode,
Assist => Key::Unidentified(native),
BrightnessDown => Key::BrightnessDown,
BrightnessUp => Key::BrightnessUp,
MediaAudioTrack => Key::MediaAudioTrack,
Sleep => Key::Standby,
Wakeup => Key::WakeUp,
Pairing => Key::Pairing,
MediaTopMenu => Key::MediaTopMenu,
Keycode11 => Key::Unidentified(native),
Keycode12 => Key::Unidentified(native),
LastChannel => Key::MediaLast,
TvDataService => Key::TVDataService,
VoiceAssist => Key::VoiceDial,
TvRadioService => Key::TVRadioService,
TvTeletext => Key::Teletext,
TvNumberEntry => Key::TVNumberEntry,
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
TvTerrestrialDigital => Key::TVTerrestrialDigital,
TvSatellite => Key::TVSatellite,
TvSatelliteBs => Key::TVSatelliteBS,
TvSatelliteCs => Key::TVSatelliteCS,
TvSatelliteService => Key::TVSatelliteToggle,
TvNetwork => Key::TVNetwork,
TvAntennaCable => Key::TVAntennaCable,
TvInputHdmi1 => Key::TVInputHDMI1,
TvInputHdmi2 => Key::TVInputHDMI2,
TvInputHdmi3 => Key::TVInputHDMI3,
TvInputHdmi4 => Key::TVInputHDMI4,
TvInputComposite1 => Key::TVInputComposite1,
TvInputComposite2 => Key::TVInputComposite2,
TvInputComponent1 => Key::TVInputComponent1,
TvInputComponent2 => Key::TVInputComponent2,
TvInputVga1 => Key::TVInputVGA1,
TvAudioDescription => Key::TVAudioDescription,
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
TvZoomMode => Key::ZoomToggle,
TvContentsMenu => Key::TVContentsMenu,
TvMediaContextMenu => Key::TVMediaContext,
TvTimerProgramming => Key::TVTimer,
Help => Key::Help,
NavigatePrevious => Key::NavigatePrevious,
NavigateNext => Key::NavigateNext,
NavigateIn => Key::NavigateIn,
NavigateOut => Key::NavigateOut,
StemPrimary => Key::Unidentified(native),
Stem1 => Key::Unidentified(native),
Stem2 => Key::Unidentified(native),
Stem3 => Key::Unidentified(native),
DpadUpLeft => Key::Unidentified(native),
DpadDownLeft => Key::Unidentified(native),
DpadUpRight => Key::Unidentified(native),
DpadDownRight => Key::Unidentified(native),
MediaSkipForward => Key::MediaSkipForward,
MediaSkipBackward => Key::MediaSkipBackward,
MediaStepForward => Key::MediaStepForward,
MediaStepBackward => Key::MediaStepBackward,
SoftSleep => Key::Unidentified(native),
Cut => Key::Cut,
Copy => Key::Copy,
Paste => Key::Paste,
SystemNavigationUp => Key::Unidentified(native),
SystemNavigationDown => Key::Unidentified(native),
SystemNavigationLeft => Key::Unidentified(native),
SystemNavigationRight => Key::Unidentified(native),
AllApps => Key::Unidentified(native),
Refresh => Key::BrowserRefresh,
ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native),
}
}
pub fn to_location(keycode: Keycode) -> KeyLocation {
use android_activity::input::Keycode::*;
match keycode {
AltLeft => KeyLocation::Left,
AltRight => KeyLocation::Right,
ShiftLeft => KeyLocation::Left,
ShiftRight => KeyLocation::Right,
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => KeyLocation::Left,
CtrlLeft => KeyLocation::Left,
CtrlRight => KeyLocation::Right,
MetaLeft => KeyLocation::Left,
MetaRight => KeyLocation::Right,
NumLock => KeyLocation::Numpad,
Numpad0 => KeyLocation::Numpad,
Numpad1 => KeyLocation::Numpad,
Numpad2 => KeyLocation::Numpad,
Numpad3 => KeyLocation::Numpad,
Numpad4 => KeyLocation::Numpad,
Numpad5 => KeyLocation::Numpad,
Numpad6 => KeyLocation::Numpad,
Numpad7 => KeyLocation::Numpad,
Numpad8 => KeyLocation::Numpad,
Numpad9 => KeyLocation::Numpad,
NumpadDivide => KeyLocation::Numpad,
NumpadMultiply => KeyLocation::Numpad,
NumpadSubtract => KeyLocation::Numpad,
NumpadAdd => KeyLocation::Numpad,
NumpadDot => KeyLocation::Numpad,
NumpadComma => KeyLocation::Numpad,
NumpadEnter => KeyLocation::Numpad,
NumpadEquals => KeyLocation::Numpad,
NumpadLeftParen => KeyLocation::Numpad,
NumpadRightParen => KeyLocation::Numpad,
_ => KeyLocation::Standard,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,27 +9,21 @@ use std::{
time::Instant,
};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::foundation::{CGRect, CGSize, NSInteger, NSProcessInfo};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{msg_send, sel};
use objc::runtime::{BOOL, YES};
use once_cell::sync::Lazy;
use super::uikit::UIView;
use super::view::WinitUIWindow;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
ffi::NSOperatingSystemVersion,
ffi::{
id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer,
CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger,
NSOperatingSystemVersion, NSUInteger,
},
},
window::WindowId as RootWindowId,
};
@@ -68,25 +62,25 @@ impl Event<'static, Never> {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_windows: Vec<id>,
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<id>,
},
Launching {
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_windows: Vec<id>,
queued_events: Vec<EventWrapper>,
queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<id>,
},
ProcessingEvents {
event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<id>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<id>,
},
ProcessingRedraws {
event_handler: Box<dyn EventHandler>,
@@ -109,6 +103,28 @@ struct AppState {
waker: EventLoopWaker,
}
impl Drop for AppState {
fn drop(&mut self) {
match self.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
..
}
| &mut AppStateImpl::Launching {
ref mut queued_windows,
..
} => {
for &mut window in queued_windows {
unsafe {
let _: () = msg_send![window, release];
}
}
}
_ => {}
}
}
}
impl AppState {
// requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> {
@@ -204,9 +220,7 @@ impl AppState {
});
}
fn did_finish_launching_transition(
&mut self,
) -> (Vec<Id<WinitUIWindow, Shared>>, Vec<EventWrapper>) {
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<EventWrapper>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
@@ -363,7 +377,7 @@ impl AppState {
}
}
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow, Shared>> {
fn main_events_cleared_transition(&mut self) -> HashSet<id> {
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents {
event_handler,
@@ -456,13 +470,20 @@ impl AppState {
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
pub unsafe fn set_key_window(window: id) {
bug_assert!(
{
let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
is_window == YES
},
"set_key_window called with an incorrect type"
);
let mut this = AppState::get_mut();
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
..
} => return queued_windows.push(window.clone()),
} => return queued_windows.push(msg_send![window, retain]),
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
@@ -474,12 +495,19 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
}
}
drop(this);
window.makeKeyAndVisible();
msg_send![window, makeKeyAndVisible]
}
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
pub unsafe fn queue_gl_or_metal_redraw(window: id) {
bug_assert!(
{
let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
is_window == YES
},
"set_key_window called with an incorrect type"
);
let mut this = AppState::get_mut();
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
@@ -507,6 +535,8 @@ pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>)
panic!("Attempt to create a `Window` after the app has terminated")
}
}
drop(this);
}
// requires main thread
@@ -533,25 +563,30 @@ pub unsafe fn did_finish_launching() {
drop(this);
for window in windows {
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
let _: () = msg_send![&window, setScreen: ptr::null::<Object>()];
window.setScreen(&screen);
let controller = window.rootViewController();
window.setRootViewController(None);
window.setRootViewController(controller.as_deref());
window.makeKeyAndVisible();
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance.
// ```
let screen: id = msg_send![window, screen];
let _: id = msg_send![screen, retain];
let _: () = msg_send![window, setScreen:0 as id];
let _: () = msg_send![window, setScreen: screen];
let _: () = msg_send![screen, release];
let controller: id = msg_send![window, rootViewController];
let _: () = msg_send![window, setRootViewController:ptr::null::<()>()];
let _: () = msg_send![window, setRootViewController: controller];
let _: () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
}
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
@@ -565,7 +600,12 @@ pub unsafe fn did_finish_launching() {
// the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching
for window in windows {
window.makeKeyAndVisible();
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
let _: () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
}
}
@@ -583,12 +623,12 @@ pub unsafe fn handle_wakeup_transition() {
}
// requires main thread
pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) {
pub unsafe fn handle_nonuser_event(event: EventWrapper) {
handle_nonuser_events(std::iter::once(event))
}
// requires main thread
pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
let mut this = AppState::get_mut();
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
@@ -766,7 +806,9 @@ pub unsafe fn handle_main_events_cleared() {
let mut redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
.map(|window| {
EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))
})
.collect();
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
@@ -799,13 +841,13 @@ fn handle_event_proxy(
EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window,
window_id,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window,
window_id,
),
}
}
@@ -815,34 +857,39 @@ fn handle_hidpi_proxy(
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window: Id<WinitUIWindow, Shared>,
window_id: id,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
let event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
window_id: RootWindowId(window_id.into()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
};
event_handler.handle_nonuser_event(event, &mut control_flow);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let (view, screen_frame) = get_view_and_screen_frame(window_id);
let physical_size = *new_inner_size;
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size.width, logical_size.height);
let size = CGSize::new(logical_size);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
view.setFrame(new_frame);
unsafe {
let _: () = msg_send![view, setFrame: new_frame];
}
}
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView, Shared>, CGRect) {
let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap();
let bounds = window.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space);
(view, screen_frame)
fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) {
unsafe {
let view_controller: id = msg_send![window_id, rootViewController];
let view: id = msg_send![view_controller, view];
let bounds: CGRect = msg_send![window_id, bounds];
let screen: id = msg_send![window_id, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![window_id, convertRect:bounds toCoordinateSpace:screen_space];
(view, screen_frame)
}
}
struct EventLoopWaker {
@@ -971,9 +1018,9 @@ impl NSOperatingSystemVersion {
pub fn os_capabilities() -> OSCapabilities {
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
let version: NSOperatingSystemVersion = unsafe {
let process_info = NSProcessInfo::process_info();
let atleast_ios_8: bool = msg_send![
&process_info,
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
let atleast_ios_8: BOOL = msg_send![
process_info,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
@@ -983,8 +1030,11 @@ pub fn os_capabilities() -> OSCapabilities {
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
msg_send![&process_info, operatingSystemVersion]
assert!(
atleast_ios_8 == YES,
"`winit` requires iOS version 8 or greater"
);
msg_send![process_info, operatingSystemVersion]
};
version.into()
});

View File

@@ -3,44 +3,47 @@ use std::{
ffi::c_void,
fmt::{self, Debug},
marker::PhantomData,
ptr,
mem, ptr,
sync::mpsc::{self, Receiver, Sender},
};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::foundation::{MainThreadMarker, NSString};
use objc2::rc::{Id, Shared};
use objc2::ClassType;
use objc::runtime::Object;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle};
use crate::{
dpi::LogicalSize,
event::Event,
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
},
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::Idiom,
};
use crate::platform_impl::platform::{
app_state,
ffi::{
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, UIApplicationMain,
UIUserInterfaceIdiom,
},
monitor, view, MonitorHandle,
};
#[derive(Debug)]
pub(crate) enum EventWrapper {
pub enum EventWrapper {
StaticEvent(Event<'static, Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub(crate) enum EventProxy {
pub enum EventProxy {
DpiChangedProxy {
window: Id<WinitUIWindow, Shared>,
window_id: id,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
@@ -53,13 +56,15 @@ pub struct EventLoopWindowTarget<T: 'static> {
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(MainThreadMarker::new().unwrap())
// guaranteed to be on main thread
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
// guaranteed to be on main thread
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
@@ -76,16 +81,16 @@ pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
static mut SINGLETON_INIT: bool = false;
unsafe {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
assert!(
!SINGLETON_INIT,
"Only one `EventLoop` is supported on iOS. \
`EventLoopProxy` might be helpful"
);
SINGLETON_INIT = true;
view::create_delegate_class();
}
let (sender_to_clone, receiver) = mpsc::channel();
@@ -109,26 +114,24 @@ impl<T: 'static> EventLoop<T> {
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let application = UIApplication::shared(MainThreadMarker::new().unwrap());
assert!(
application.is_none(),
let application: *mut Object = msg_send![class!(UIApplication), sharedApplication];
assert_eq!(
application,
ptr::null_mut(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
);
app_state::will_launch(Box::new(EventLoopHandler {
f: event_handler,
event_loop: self.window_target,
}));
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
UIApplicationMain(
0,
ptr::null(),
None,
Some(&NSString::from_str("WinitApplicationDelegate")),
nil,
NSStringRust::alloc(nil).init_str("AppDelegate"),
);
unreachable!()
}
@@ -146,9 +149,8 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
UIDevice::current(MainThreadMarker::new().unwrap())
.userInterfaceIdiom()
.into()
// guaranteed to be on main thread
unsafe { self::get_idiom() }
}
}
@@ -178,23 +180,14 @@ impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
// we want all the members of context to be zero/null, except one
let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = Some(event_loop_proxy_handler);
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
@@ -230,6 +223,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -281,7 +275,7 @@ fn setup_control_flow_observers() {
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopAfterWaiting,
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::min_value(),
control_flow_begin_handler,
@@ -351,3 +345,10 @@ where
}
}
}
// must be called on main thread
pub unsafe fn get_idiom() -> Idiom {
let device: id = msg_send![class!(UIDevice), currentDevice];
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
raw_idiom.into()
}

View File

@@ -1,11 +1,24 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::convert::TryInto;
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSUInteger};
use objc::{runtime::Object, Encode, Encoding};
use crate::platform::ios::{Idiom, ScreenEdge};
use crate::{
dpi::LogicalSize,
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
};
pub type id = *mut Object;
pub const nil: id = 0 as id;
#[cfg(target_pointer_width = "32")]
pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
pub type NSInteger = isize;
pub type NSUInteger = usize;
#[repr(C)]
#[derive(Clone, Debug)]
@@ -15,15 +28,91 @@ pub struct NSOperatingSystemVersion {
pub patch: NSInteger,
}
unsafe impl Encode for NSOperatingSystemVersion {
const ENCODING: Encoding = Encoding::Struct(
"NSOperatingSystemVersion",
&[
NSInteger::ENCODING,
NSInteger::ENCODING,
NSInteger::ENCODING,
],
);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
}
impl CGSize {
pub fn new(size: LogicalSize<f64>) -> CGSize {
CGSize {
width: size.width as _,
height: size.height as _,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
impl CGRect {
pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
CGRect { origin, size }
}
}
unsafe impl Encode for CGRect {
fn encode() -> Encoding {
unsafe {
if cfg!(target_pointer_width = "32") {
Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}")
} else if cfg!(target_pointer_width = "64") {
Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}")
} else {
unimplemented!()
}
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
#[repr(transparent)]
@@ -31,7 +120,9 @@ unsafe impl Encode for NSOperatingSystemVersion {
pub struct UIUserInterfaceIdiom(NSInteger);
unsafe impl Encode for UIUserInterfaceIdiom {
const ENCODING: Encoding = NSInteger::ENCODING;
fn encode() -> Encoding {
NSInteger::encode()
}
}
impl UIUserInterfaceIdiom {
@@ -66,12 +157,65 @@ impl From<UIUserInterfaceIdiom> for Idiom {
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct UIInterfaceOrientationMask(NSUInteger);
unsafe impl Encode for UIInterfaceOrientationMask {
fn encode() -> Encoding {
NSUInteger::encode()
}
}
impl UIInterfaceOrientationMask {
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
pub const Landscape: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
pub const AllButUpsideDown: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
pub const All: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
}
impl BitOr for UIInterfaceOrientationMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
UIInterfaceOrientationMask(self.0 | rhs.0)
}
}
impl UIInterfaceOrientationMask {
pub fn from_valid_orientations_idiom(
valid_orientations: ValidOrientations,
idiom: Idiom,
) -> UIInterfaceOrientationMask {
match (valid_orientations, idiom) {
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => {
UIInterfaceOrientationMask::AllButUpsideDown
}
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
(ValidOrientations::Portrait, _) => {
UIInterfaceOrientationMask::Portrait
| UIInterfaceOrientationMask::PortraitUpsideDown
}
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING;
fn encode() -> Encoding {
NSUInteger::encode()
}
}
impl From<ScreenEdge> for UIRectEdge {
@@ -91,3 +235,158 @@ impl From<UIRectEdge> for ScreenEdge {
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
fn encode() -> Encoding {
NSInteger::encode()
}
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
}
#[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode;
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
principalClassName: id,
delegateClassName: id,
) -> c_int;
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef,
activities: CFOptionFlags,
repeats: Boolean,
order: CFIndex,
callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) -> CFRunLoopObserverRef;
pub fn CFRunLoopAddObserver(
rl: CFRunLoopRef,
observer: CFRunLoopObserverRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerCreate(
allocator: CFAllocatorRef,
fireDate: CFAbsoluteTime,
interval: CFTimeInterval,
flags: CFOptionFlags,
order: CFIndex,
callout: CFRunLoopTimerCallBack,
context: *mut CFRunLoopTimerContext,
) -> CFRunLoopTimerRef;
pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode);
pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime);
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
pub fn CFRunLoopSourceCreate(
allocator: CFAllocatorRef,
order: CFIndex,
context: *mut CFRunLoopSourceContext,
) -> CFRunLoopSourceRef;
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
pub fn CFRelease(cftype: *const c_void);
}
pub type Boolean = u8;
pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {}
pub type CFRunLoopRef = *mut CFRunLoop;
pub type CFRunLoopMode = CFStringRef;
pub enum CFRunLoopObserver {}
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
pub enum CFRunLoopTimer {}
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
pub enum CFRunLoopSource {}
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
pub enum CFString {}
pub type CFStringRef = *const CFString;
pub type CFHashCode = c_ulong;
pub type CFIndex = c_long;
pub type CFOptionFlags = c_ulong;
pub type CFRunLoopActivity = CFOptionFlags;
pub type CFAbsoluteTime = CFTimeInterval;
pub type CFTimeInterval = f64;
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
pub type CFRunLoopObserverCallBack =
extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void);
pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
pub enum CFRunLoopObserverContext {}
pub enum CFRunLoopTimerContext {}
#[repr(C)]
pub struct CFRunLoopSourceContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
pub release: Option<extern "C" fn(*const c_void)>,
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub perform: Option<extern "C" fn(*mut c_void)>,
}
// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of
// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy
// so please test if you change the name back to NSString.
pub trait NSStringRust: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSString), alloc]
}
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
unsafe fn stringByAppendingString_(self, other: id) -> id;
unsafe fn init_str(self, string: &str) -> Self;
unsafe fn UTF8String(self) -> *const c_char;
}
impl NSStringRust for id {
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
msg_send![self, initWithUTF8String: c_string]
}
unsafe fn stringByAppendingString_(self, other: id) -> id {
msg_send![self, stringByAppendingString: other]
}
unsafe fn init_str(self, string: &str) -> id {
let cstring = CString::new(string).unwrap();
self.initWithUTF8String_(cstring.as_ptr())
}
unsafe fn UTF8String(self) -> *const c_char {
msg_send![self, UTF8String]
}
}

View File

@@ -55,7 +55,7 @@
//!
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
#![cfg(ios_platform)]
#![cfg(target_os = "ios")]
#![allow(clippy::let_unit_value)]
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
@@ -63,7 +63,8 @@
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
if !::objc2::foundation::is_main_thread() {
let is_main_thread: ::objc::runtime::BOOL = msg_send!(class!(NSThread), isMainThread);
if is_main_thread == ::objc::runtime::NO {
panic!($($t)*);
}
};
@@ -73,7 +74,6 @@ mod app_state;
mod event_loop;
mod ffi;
mod monitor;
mod uikit;
mod view;
mod window;
@@ -87,19 +87,17 @@ pub(crate) use self::{
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
use self::uikit::UIScreen;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {
uiscreen: *const UIScreen,
uiscreen: ffi::id,
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
DeviceId {
uiscreen: std::ptr::null(),
uiscreen: std::ptr::null_mut(),
}
}
}
@@ -107,9 +105,6 @@ impl DeviceId {
unsafe impl Send for DeviceId {}
unsafe impl Sync for DeviceId {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}
#[derive(Debug)]
pub enum OsError {}

View File

@@ -1,48 +1,74 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::{BTreeSet, VecDeque},
fmt,
ops::{Deref, DerefMut},
};
use objc2::foundation::{MainThreadMarker, NSInteger};
use objc2::rc::{Id, Shared};
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode as RootVideoMode,
platform_impl::platform::app_state,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{
app_state,
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
},
};
// TODO(madsmtm): Remove or refactor this
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode, Shared>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: ScreenModeSendSync,
pub(crate) screen_mode: NativeDisplayMode,
pub(crate) monitor: MonitorHandle,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub id);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.0, release];
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
let _: id = msg_send![self.0, retain];
}
NativeDisplayMode(self.0)
}
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate_millihertz: self.refresh_rate_millihertz,
screen_mode: self.screen_mode.clone(),
monitor: self.monitor.clone(),
}
}
}
impl VideoMode {
fn new(uiscreen: Id<UIScreen, Shared>, screen_mode: Id<UIScreenMode, Shared>) -> VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
let size: CGSize = msg_send![screen_mode, size];
let screen_mode: id = msg_send![screen_mode, retain];
let screen_mode = NativeDisplayMode(screen_mode);
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate_millihertz,
screen_mode: ScreenModeSendSync(screen_mode),
monitor: MonitorHandle::new(uiscreen),
screen_mode,
monitor: MonitorHandle::retained_new(uiscreen),
}
}
@@ -58,46 +84,51 @@ impl VideoMode {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Inner {
uiscreen: Id<UIScreen, Shared>,
uiscreen: id,
}
#[derive(Clone, PartialEq, Eq, Hash)]
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.uiscreen, release];
}
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle {
inner: Inner,
}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self))
}
}
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
unsafe {
assert_main_thread!(
"`MonitorHandle` methods can only be run on the main thread on iOS"
);
}
&self.inner
}
}
impl DerefMut for MonitorHandle {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
unsafe {
assert_main_thread!(
"`MonitorHandle` methods can only be run on the main thread on iOS"
);
}
&mut self.inner
}
}
@@ -105,9 +136,17 @@ impl DerefMut for MonitorHandle {
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Clone for MonitorHandle {
fn clone(&self) -> MonitorHandle {
MonitorHandle::retained_new(self.uiscreen)
}
}
impl Drop for MonitorHandle {
fn drop(&mut self) {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
unsafe {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
}
@@ -135,9 +174,12 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
Self {
pub fn retained_new(uiscreen: id) -> MonitorHandle {
unsafe {
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
let _: id = msg_send![uiscreen, retain];
}
MonitorHandle {
inner: Inner { uiscreen },
}
}
@@ -145,62 +187,69 @@ impl MonitorHandle {
impl Inner {
pub fn name(&self) -> Option<String> {
let main = UIScreen::main(MainThreadMarker::new().unwrap());
if self.uiscreen == main {
Some("Primary".to_string())
} else if self.uiscreen == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(MainThreadMarker::new().unwrap())
.iter()
.position(|rhs| rhs == &*self.uiscreen)
.map(|idx| idx.to_string())
unsafe {
let main = main_uiscreen();
if self.uiscreen == main.uiscreen {
Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
Some("Mirrored".to_string())
} else {
uiscreens()
.iter()
.position(|rhs| rhs.uiscreen == self.uiscreen)
.map(|idx| idx.to_string())
}
}
}
pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self.uiscreen.nativeBounds();
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
}
pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self.uiscreen.nativeBounds();
(bounds.origin.x as f64, bounds.origin.y as f64).into()
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
}
pub fn scale_factor(&self) -> f64 {
self.uiscreen.nativeScale() as f64
unsafe {
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
scale as f64
}
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(&self.uiscreen))
Some(refresh_rate_millihertz(self.uiscreen))
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = self
.uiscreen
.availableModes()
.into_iter()
.map(|mode| {
let mode: *const UIScreenMode = mode;
let mode = unsafe { Id::retain(mode as *mut UIScreenMode).unwrap() };
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let mut modes = BTreeSet::new();
unsafe {
let available_modes: id = msg_send![self.uiscreen, availableModes];
let available_mode_count: NSUInteger = msg_send![available_modes, count];
RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
}
})
.collect();
for i in 0..available_mode_count {
let mode: id = msg_send![available_modes, objectAtIndex: i];
modes.insert(RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
});
}
}
modes.into_iter().map(|mode| mode.video_mode)
modes.into_iter()
}
}
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
let refresh_rate_millihertz: NSInteger = {
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
let refresh_rate_millihertz: NSInteger = unsafe {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
uiscreen.maximumFramesPerSecond()
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
@@ -222,25 +271,43 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
// MonitorHandleExtIOS
impl Inner {
pub(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
&self.uiscreen
pub fn ui_screen(&self) -> id {
self.uiscreen
}
pub fn preferred_video_mode(&self) -> VideoMode {
VideoMode::new(
self.uiscreen.clone(),
self.uiscreen.preferredMode().unwrap(),
)
pub fn preferred_video_mode(&self) -> RootVideoMode {
unsafe {
let mode: id = msg_send![self.uiscreen, preferredMode];
RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
}
}
}
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()
.map(|screen| {
let screen: *const UIScreen = screen;
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
MonitorHandle::new(screen)
})
.collect()
// requires being run on main thread
pub unsafe fn main_uiscreen() -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
let screens: id = msg_send![class!(UIScreen), screens];
let count: NSUInteger = msg_send![screens, count];
let mut result = VecDeque::with_capacity(count as _);
let screens_enum: id = msg_send![screens, objectEnumerator];
loop {
let screen: id = msg_send![screens_enum, nextObject];
if screen == nil {
break result;
}
result.push_back(MonitorHandle::retained_new(screen));
}
}

View File

@@ -1,30 +0,0 @@
use objc2::foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIWindow};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIApplication;
unsafe impl ClassType for UIApplication {
#[inherits(NSObject)]
type Super = UIResponder;
}
);
extern_methods!(
unsafe impl UIApplication {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self, Shared>> {
unsafe { msg_send_id![Self::class(), sharedApplication] }
}
pub fn windows(&self) -> Id<NSArray<UIWindow, Shared>, Shared> {
unsafe { msg_send_id![self, windows] }
}
#[sel(statusBarFrame)]
pub fn statusBarFrame(&self) -> CGRect;
}
);

View File

@@ -1,11 +0,0 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UICoordinateSpace;
unsafe impl ClassType for UICoordinateSpace {
type Super = NSObject;
}
);

View File

@@ -1,25 +0,0 @@
use objc2::foundation::{MainThreadMarker, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::super::ffi::UIUserInterfaceIdiom;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIDevice;
unsafe impl ClassType for UIDevice {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), currentDevice] }
}
#[sel(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
}
);

View File

@@ -1,11 +0,0 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIEvent;
unsafe impl ClassType for UIEvent {
type Super = NSObject;
}
);

View File

@@ -1,44 +0,0 @@
#![deny(unsafe_op_in_unsafe_fn)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use std::os::raw::{c_char, c_int};
use objc2::foundation::NSString;
mod application;
mod coordinate_space;
mod device;
mod event;
mod responder;
mod screen;
mod screen_mode;
mod touch;
mod trait_collection;
mod view;
mod view_controller;
mod window;
pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::UIDevice;
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::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)]
pub(crate) use self::view::{UIEdgeInsets, UIView};
pub(crate) use self::view_controller::{UIInterfaceOrientationMask, UIViewController};
pub(crate) use self::window::UIWindow;
#[link(name = "UIKit", kind = "framework")]
extern "C" {
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
principalClassName: Option<&NSString>,
delegateClassName: Option<&NSString>,
) -> c_int;
}

View File

@@ -1,11 +0,0 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIResponder;
unsafe impl ClassType for UIResponder {
type Super = NSObject;
}
);

View File

@@ -1,79 +0,0 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UICoordinateSpace, UIScreenMode};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIScreen;
unsafe impl ClassType for UIScreen {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIScreen {
pub fn main(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mainScreen] }
}
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self, Shared>, Shared> {
unsafe { msg_send_id![Self::class(), screens] }
}
#[sel(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(scale)]
pub fn scale(&self) -> CGFloat;
#[sel(nativeBounds)]
pub fn nativeBounds(&self) -> CGRect;
#[sel(nativeScale)]
pub fn nativeScale(&self) -> CGFloat;
#[sel(maximumFramesPerSecond)]
pub fn maximumFramesPerSecond(&self) -> NSInteger;
pub fn mirroredScreen(&self) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mirroredScreen] }
}
pub fn preferredMode(&self) -> Option<Id<UIScreenMode, Shared>> {
unsafe { msg_send_id![self, preferredMode] }
}
#[sel(setCurrentMode:)]
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode, Shared>, Shared> {
unsafe { msg_send_id![self, availableModes] }
}
#[sel(setOverscanCompensation:)]
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace, Shared> {
unsafe { msg_send_id![self, coordinateSpace] }
}
}
);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: Self = Self(0);
pub const InsetBounds: Self = Self(1);
pub const None: Self = Self(2);
}

View File

@@ -1,18 +0,0 @@
use objc2::foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIScreenMode;
unsafe impl ClassType for UIScreenMode {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIScreenMode {
#[sel(size)]
pub fn size(&self) -> CGSize;
}
);

View File

@@ -1,64 +0,0 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use super::UIView;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITouch;
unsafe impl ClassType for UITouch {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UITouch {
#[sel(locationInView:)]
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
#[sel(type)]
pub fn type_(&self) -> UITouchType;
#[sel(force)]
pub fn force(&self) -> CGFloat;
#[sel(maximumPossibleForce)]
pub fn maximumPossibleForce(&self) -> CGFloat;
#[sel(altitudeAngle)]
pub fn altitudeAngle(&self) -> CGFloat;
#[sel(phase)]
pub fn phase(&self) -> UITouchPhase;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
unsafe impl Encode for UITouchType {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
unsafe impl Encode for UITouchPhase {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -1,32 +0,0 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITraitCollection;
unsafe impl ClassType for UITraitCollection {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UITraitCollection {
#[sel(forceTouchCapability)]
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
unsafe impl Encode for UIForceTouchCapability {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -1,89 +0,0 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UICoordinateSpace, UIResponder, UIViewController};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIView;
unsafe impl ClassType for UIView {
#[inherits(NSObject)]
type Super = UIResponder;
}
);
extern_methods!(
unsafe impl UIView {
#[sel(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(setBounds:)]
pub fn setBounds(&self, value: CGRect);
#[sel(frame)]
pub fn frame(&self) -> CGRect;
#[sel(setFrame:)]
pub fn setFrame(&self, value: CGRect);
#[sel(contentScaleFactor)]
pub fn contentScaleFactor(&self) -> CGFloat;
#[sel(setContentScaleFactor:)]
pub fn setContentScaleFactor(&self, val: CGFloat);
#[sel(setMultipleTouchEnabled:)]
pub fn setMultipleTouchEnabled(&self, val: bool);
pub fn rootViewController(&self) -> Option<Id<UIViewController, Shared>> {
unsafe { msg_send_id![self, rootViewController] }
}
#[sel(setRootViewController:)]
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
#[sel(convertRect:toCoordinateSpace:)]
pub fn convertRect_toCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(convertRect:fromCoordinateSpace:)]
pub fn convertRect_fromCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(safeAreaInsets)]
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
#[sel(setNeedsDisplay)]
pub fn setNeedsDisplay(&self);
}
);
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
unsafe impl Encode for UIEdgeInsets {
const ENCODING: Encoding = Encoding::Struct(
"UIEdgeInsets",
&[
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
],
);
}

View File

@@ -1,56 +0,0 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSObject, NSUInteger};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIViewController;
unsafe impl ClassType for UIViewController {
#[inherits(NSObject)]
type Super = UIResponder;
}
);
extern_methods!(
unsafe impl UIViewController {
#[sel(attemptRotationToDeviceOrientation)]
pub fn attemptRotationToDeviceOrientation();
#[sel(setNeedsStatusBarAppearanceUpdate)]
pub fn setNeedsStatusBarAppearanceUpdate(&self);
#[sel(setNeedsUpdateOfHomeIndicatorAutoHidden)]
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
#[sel(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
pub fn view(&self) -> Option<Id<UIView, Shared>> {
unsafe { msg_send_id![self, view] }
}
#[sel(setView:)]
pub fn setView(&self, view: Option<&UIView>);
}
);
bitflags! {
#[derive(Clone, Copy)]
pub struct UIInterfaceOrientationMask: NSUInteger {
const Portrait = 1 << 1;
const PortraitUpsideDown = 1 << 2;
const LandscapeRight = 1 << 3;
const LandscapeLeft = 1 << 4;
const Landscape = Self::LandscapeLeft.bits() | Self::LandscapeRight.bits();
const AllButUpsideDown = Self::Landscape.bits() | Self::Portrait.bits();
const All = Self::AllButUpsideDown.bits() | Self::PortraitUpsideDown.bits();
}
}
unsafe impl Encode for UIInterfaceOrientationMask {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -1,35 +0,0 @@
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIScreen, UIView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIWindow;
unsafe impl ClassType for UIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
}
);
extern_methods!(
unsafe impl UIWindow {
pub fn screen(&self) -> Id<UIScreen, Shared> {
unsafe { msg_send_id![self, screen] }
}
#[sel(setScreen:)]
pub fn setScreen(&self, screen: &UIScreen);
#[sel(setHidden:)]
pub fn setHidden(&self, flag: bool);
#[sel(makeKeyAndVisible)]
pub fn makeKeyAndVisible(&self);
#[sel(isKeyWindow)]
pub fn isKeyWindow(&self) -> bool;
}
);

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +1,64 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::VecDeque,
ffi::c_void,
ops::{Deref, DerefMut},
};
use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{class, msg_send};
use objc::runtime::{Class, Object, BOOL, NO, YES};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
platform::ios::{ScreenEdge, ValidOrientations},
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
ffi::UIRectEdge,
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
event_loop::{self, EventProxy, EventWrapper},
ffi::{
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
UIRectEdge, UIScreenOverscanCompensation,
},
monitor, view, EventLoopWindowTarget, MonitorHandle,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
},
};
pub struct Inner {
pub(crate) window: Id<WinitUIWindow, Shared>,
pub(crate) view_controller: Id<WinitViewController, Shared>,
pub(crate) view: Id<WinitView, Shared>,
pub window: id,
pub view_controller: id,
pub view: id,
gl_or_metal_backed: bool,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.view, release];
let _: () = msg_send![self.view_controller, release];
let _: () = msg_send![self.window, release];
}
}
}
impl Inner {
pub fn set_title(&self, _title: &str) {
debug!("`Window::set_title` is ignored on iOS")
}
pub fn set_transparent(&self, _transparent: bool) {
debug!("`Window::set_transparent` is ignored on iOS")
}
pub fn set_visible(&self, visible: bool) {
self.window.setHidden(!visible)
match visible {
true => unsafe {
let _: () = msg_send![self.window, setHidden: NO];
},
false => unsafe {
let _: () = msg_send![self.window, setHidden: YES];
},
}
}
pub fn is_visible(&self) -> Option<bool> {
@@ -68,9 +77,9 @@ impl Inner {
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window.clone());
app_state::queue_gl_or_metal_redraw(self.window);
} else {
self.view.setNeedsDisplay();
let _: () = msg_send![self.view, setNeedsDisplay];
}
}
}
@@ -112,7 +121,7 @@ impl Inner {
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
self.window.setBounds(bounds);
let _: () = msg_send![self.window, setBounds: bounds];
}
}
@@ -152,15 +161,6 @@ impl Inner {
warn!("`Window::set_max_inner_size` is ignored on iOS")
}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
warn!("`Window::set_resize_increments` is ignored on iOS")
}
pub fn set_resizable(&self, _resizable: bool) {
warn!("`Window::set_resizable` is ignored on iOS")
}
@@ -170,19 +170,11 @@ impl Inner {
false
}
#[inline]
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
warn!("`Window::set_enabled_buttons` is ignored on iOS");
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
warn!("`Window::enabled_buttons` is ignored on iOS");
WindowButtons::all()
}
pub fn scale_factor(&self) -> f64 {
self.view.contentScaleFactor() as _
unsafe {
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
hidpi as _
}
}
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
@@ -205,10 +197,6 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -217,11 +205,6 @@ impl Inner {
warn!("`Window::set_minimized` is ignored on iOS")
}
pub fn is_minimized(&self) -> Option<bool> {
warn!("`Window::is_minimized` is ignored on iOS");
None
}
pub fn set_maximized(&self, _maximized: bool) {
warn!("`Window::set_maximized` is ignored on iOS")
}
@@ -231,42 +214,49 @@ impl Inner {
false
}
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen();
uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
uiscreen.clone()
}
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
};
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe {
let uiscreen = match monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
let _: () =
msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
uiscreen
}
Some(Fullscreen::Borderless(monitor)) => monitor
.unwrap_or_else(|| self.current_monitor_inner())
.ui_screen() as id,
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
};
// this is pretty slow on iOS, so avoid doing it if we can
let current = self.window.screen();
if uiscreen != current {
self.window.setScreen(&uiscreen);
// this is pretty slow on iOS, so avoid doing it if we can
let current: id = msg_send![self.window, screen];
if uiscreen != current {
let _: () = msg_send![self.window, setScreen: uiscreen];
}
let bounds: CGRect = msg_send![uiscreen, bounds];
let _: () = msg_send![self.window, setFrame: bounds];
// For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on
// each side
let _: () = msg_send![
uiscreen,
setOverscanCompensation: UIScreenOverscanCompensation::None
];
}
let bounds = uiscreen.bounds();
self.window.setFrame(bounds);
// For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on
// each side
uiscreen.setOverscanCompensation(UIScreenOverscanCompensation::None);
}
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
unsafe {
let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen();
let uiscreen = monitor.inner.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds();
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
@@ -281,32 +271,31 @@ impl Inner {
}
}
pub fn set_decorations(&self, _decorations: bool) {}
pub fn set_decorations(&self, _decorations: bool) {
warn!("`Window::set_decorations` is ignored on iOS")
}
pub fn is_decorated(&self) -> bool {
warn!("`Window::is_decorated` is ignored on iOS");
true
}
pub fn set_window_level(&self, _level: WindowLevel) {
warn!("`Window::set_window_level` is ignored on iOS")
pub fn set_always_on_top(&self, _always_on_top: bool) {
warn!("`Window::set_always_on_top` is ignored on iOS")
}
pub fn set_window_icon(&self, _icon: Option<Icon>) {
warn!("`Window::set_window_icon` is ignored on iOS")
}
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
pub fn set_ime_position(&self, _position: Position) {
warn!("`Window::set_ime_position` is ignored on iOS")
}
pub fn set_ime_allowed(&self, _allowed: bool) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn focus_window(&self) {
warn!("`Window::set_focus` is ignored on iOS")
}
@@ -316,62 +305,43 @@ impl Inner {
}
// Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> MonitorHandle {
MonitorHandle::new(self.window.screen())
fn current_monitor_inner(&self) -> RootMonitorHandle {
unsafe {
let uiscreen: id = msg_send![self.window, screen];
RootMonitorHandle {
inner: MonitorHandle::retained_new(uiscreen),
}
}
}
pub fn current_monitor(&self) -> Option<MonitorHandle> {
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
Some(self.current_monitor_inner())
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(MainThreadMarker::new().unwrap())
unsafe { monitor::uiscreens() }
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
}
pub fn id(&self) -> WindowId {
self.window.id()
self.window.into()
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = UiKitWindowHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
window_handle.ui_window = self.window as _;
window_handle.ui_view = self.view as _;
window_handle.ui_view_controller = self.view_controller as _;
RawWindowHandle::UiKit(window_handle)
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
}
pub fn theme(&self) -> Option<Theme> {
warn!("`Window::theme` is ignored on iOS");
None
}
pub fn has_focus(&self) -> bool {
self.window.isKeyWindow()
}
#[inline]
pub fn set_theme(&self, _theme: Option<Theme>) {
warn!("`Window::set_theme` is ignored on iOS");
}
pub fn title(&self) -> String {
warn!("`Window::title` is ignored on iOS");
String::new()
}
pub fn reset_dead_keys(&self) {
// Noop
}
}
pub struct Window {
@@ -380,7 +350,9 @@ pub struct Window {
impl Drop for Window {
fn drop(&mut self) {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
unsafe {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
}
}
@@ -391,14 +363,18 @@ impl Deref for Window {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
&self.inner
}
}
impl DerefMut for Window {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
&mut self.inner
}
}
@@ -409,174 +385,210 @@ impl Window {
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
let mtm = MainThreadMarker::new().unwrap();
if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
}
if window_attributes.max_inner_size.is_some() {
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
}
if window_attributes.always_on_top {
warn!("`WindowAttributes::always_on_top` is unsupported on iOS");
}
// TODO: transparency, visible
let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(),
Some(Fullscreen::Borderless(None)) | None => &main_screen,
};
let screen_bounds = screen.bounds();
let frame = match window_attributes.inner_size {
Some(dim) => {
let scale_factor = screen.scale();
let size = dim.to_logical::<f64>(scale_factor as f64);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: size.width as _,
height: size.height as _,
},
unsafe {
let screen = match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => {
video_mode.video_mode.monitor.ui_screen() as id
}
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(),
Some(Fullscreen::Borderless(None)) | None => {
monitor::main_uiscreen().ui_screen() as id
}
}
None => screen_bounds,
};
let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = unsafe {
let layer_class = WinitView::layerClass();
let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal || is_gl
};
let view_controller =
WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view);
let window = WinitUIWindow::new(
mtm,
&window_attributes,
&platform_attributes,
frame,
&view_controller,
);
unsafe { app_state::set_key_window(&window) };
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor = view.contentScaleFactor();
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let bounds = view.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
let window_id = RootWindowId(window.id());
unsafe {
let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size {
Some(dim) => {
let scale_factor: CGFloat = msg_send![screen, scale];
let size = dim.to_logical::<f64>(scale_factor as f64);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: size.width as _,
height: size.height as _,
},
}
}
None => screen_bounds,
};
let view = view::create_view(&window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = {
let view_class: *const Class = msg_send![view, class];
let layer_class: *const Class = msg_send![view_class, layerClass];
let is_metal: BOOL =
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal == YES || is_gl == YES
};
let view_controller =
view::create_view_controller(&window_attributes, &platform_attributes, view);
let window = view::create_window(
&window_attributes,
&platform_attributes,
frame,
view_controller,
);
let result = Window {
inner: Inner {
window,
view_controller,
view,
gl_or_metal_backed,
},
};
app_state::set_key_window(window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![view, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window: window.clone(),
window_id: window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
}
Ok(Window {
inner: Inner {
window,
view_controller,
view,
gl_or_metal_backed,
},
})
Ok(result)
}
}
}
// WindowExtIOS
impl Inner {
pub fn ui_window(&self) -> *mut c_void {
Id::as_ptr(&self.window) as *mut c_void
pub fn ui_window(&self) -> id {
self.window
}
pub fn ui_view_controller(&self) -> *mut c_void {
Id::as_ptr(&self.view_controller) as *mut c_void
pub fn ui_view_controller(&self) -> id {
self.view_controller
}
pub fn ui_view(&self) -> *mut c_void {
Id::as_ptr(&self.view) as *mut c_void
pub fn ui_view(&self) -> id {
self.view
}
pub fn set_scale_factor(&self, scale_factor: f64) {
assert!(
dpi::validate_scale_factor(scale_factor),
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
);
let scale_factor = scale_factor as CGFloat;
self.view.setContentScaleFactor(scale_factor);
unsafe {
assert!(
dpi::validate_scale_factor(scale_factor),
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
);
let scale_factor = scale_factor as CGFloat;
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor];
}
}
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.view_controller.set_supported_interface_orientations(
MainThreadMarker::new().unwrap(),
valid_orientations,
);
unsafe {
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
valid_orientations,
idiom,
);
msg_send![
self.view_controller,
setSupportedInterfaceOrientations: supported_orientations
]
}
}
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.view_controller
.setPrefersHomeIndicatorAutoHidden(hidden);
unsafe {
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
let _: () = msg_send![
self.view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
}
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
self.view_controller
.setPreferredScreenEdgesDeferringSystemGestures(edges);
unsafe {
let _: () = msg_send![
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
}
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.view_controller.setPrefersStatusBarHidden(hidden);
unsafe {
let status_bar_hidden = if hidden { YES } else { NO };
let _: () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
}
}
}
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(self.window.bounds())
self.rect_to_screen_space(msg_send![self.window, bounds])
}
// requires main thread
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_toCoordinateSpace(rect, &screen_space)
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space]
} else {
rect
}
}
// requires main thread
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_fromCoordinateSpace(rect, &screen_space)
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space]
} else {
rect
}
}
// requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds = self.window.bounds();
let bounds: CGRect = msg_send![self.window, bounds];
if app_state::os_capabilities().safe_area {
let safe_area = self.window.safeAreaInsets();
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
let safe_bounds = CGRect {
origin: CGPoint {
x: bounds.origin.x + safe_area.left,
@@ -590,11 +602,13 @@ impl Inner {
self.rect_to_screen_space(safe_bounds)
} else {
let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
let status_bar_frame: CGRect = {
let app: id = msg_send![class!(UIApplication), sharedApplication];
assert!(
!app.is_null(),
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"
);
app.statusBarFrame()
msg_send![app, statusBarFrame]
};
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
(screen_frame.origin.y, screen_frame.size.height)
@@ -620,7 +634,7 @@ impl Inner {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId {
window: *mut WinitUIWindow,
window: id,
}
impl WindowId {
@@ -656,11 +670,39 @@ impl From<&Object> for WindowId {
}
}
#[derive(Clone, Default)]
impl From<&mut Object> for WindowId {
fn from(window: &mut Object) -> WindowId {
WindowId {
window: window as _,
}
}
}
impl From<id> for WindowId {
fn from(window: id) -> WindowId {
WindowId { window }
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
pub scale_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> PlatformSpecificWindowBuilderAttributes {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
scale_factor: None,
valid_orientations: Default::default(),
prefers_home_indicator_hidden: false,
prefers_status_bar_hidden: false,
preferred_screen_edges_deferring_system_gestures: Default::default(),
}
}
}

View File

@@ -1,887 +0,0 @@
//! Convert XKB keys to Winit keys.
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
/// Map the raw X11-style keycode to the `KeyCode` enum.
///
/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses.
pub fn raw_keycode_to_keycode(keycode: u32) -> KeyCode {
scancode_to_keycode(keycode.saturating_sub(8))
}
/// Map the linux scancode to Keycode.
///
/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode.
pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
// The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as
// libxkbcommon's documentation seems to suggest that the keycode values we're interested in
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
// I can only hope they agree on what the keycodes mean.
//
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are
// difficult to test the correctness of, or discover the purpose of. Because of this, they've
// either been commented out here, or not included at all.
match scancode {
0 => KeyCode::Unidentified(NativeKeyCode::Xkb(0)),
1 => KeyCode::Escape,
2 => KeyCode::Digit1,
3 => KeyCode::Digit2,
4 => KeyCode::Digit3,
5 => KeyCode::Digit4,
6 => KeyCode::Digit5,
7 => KeyCode::Digit6,
8 => KeyCode::Digit7,
9 => KeyCode::Digit8,
10 => KeyCode::Digit9,
11 => KeyCode::Digit0,
12 => KeyCode::Minus,
13 => KeyCode::Equal,
14 => KeyCode::Backspace,
15 => KeyCode::Tab,
16 => KeyCode::KeyQ,
17 => KeyCode::KeyW,
18 => KeyCode::KeyE,
19 => KeyCode::KeyR,
20 => KeyCode::KeyT,
21 => KeyCode::KeyY,
22 => KeyCode::KeyU,
23 => KeyCode::KeyI,
24 => KeyCode::KeyO,
25 => KeyCode::KeyP,
26 => KeyCode::BracketLeft,
27 => KeyCode::BracketRight,
28 => KeyCode::Enter,
29 => KeyCode::ControlLeft,
30 => KeyCode::KeyA,
31 => KeyCode::KeyS,
32 => KeyCode::KeyD,
33 => KeyCode::KeyF,
34 => KeyCode::KeyG,
35 => KeyCode::KeyH,
36 => KeyCode::KeyJ,
37 => KeyCode::KeyK,
38 => KeyCode::KeyL,
39 => KeyCode::Semicolon,
40 => KeyCode::Quote,
41 => KeyCode::Backquote,
42 => KeyCode::ShiftLeft,
43 => KeyCode::Backslash,
44 => KeyCode::KeyZ,
45 => KeyCode::KeyX,
46 => KeyCode::KeyC,
47 => KeyCode::KeyV,
48 => KeyCode::KeyB,
49 => KeyCode::KeyN,
50 => KeyCode::KeyM,
51 => KeyCode::Comma,
52 => KeyCode::Period,
53 => KeyCode::Slash,
54 => KeyCode::ShiftRight,
55 => KeyCode::NumpadMultiply,
56 => KeyCode::AltLeft,
57 => KeyCode::Space,
58 => KeyCode::CapsLock,
59 => KeyCode::F1,
60 => KeyCode::F2,
61 => KeyCode::F3,
62 => KeyCode::F4,
63 => KeyCode::F5,
64 => KeyCode::F6,
65 => KeyCode::F7,
66 => KeyCode::F8,
67 => KeyCode::F9,
68 => KeyCode::F10,
69 => KeyCode::NumLock,
70 => KeyCode::ScrollLock,
71 => KeyCode::Numpad7,
72 => KeyCode::Numpad8,
73 => KeyCode::Numpad9,
74 => KeyCode::NumpadSubtract,
75 => KeyCode::Numpad4,
76 => KeyCode::Numpad5,
77 => KeyCode::Numpad6,
78 => KeyCode::NumpadAdd,
79 => KeyCode::Numpad1,
80 => KeyCode::Numpad2,
81 => KeyCode::Numpad3,
82 => KeyCode::Numpad0,
83 => KeyCode::NumpadDecimal,
85 => KeyCode::Lang5,
86 => KeyCode::IntlBackslash,
87 => KeyCode::F11,
88 => KeyCode::F12,
89 => KeyCode::IntlRo,
90 => KeyCode::Lang3,
91 => KeyCode::Lang4,
92 => KeyCode::Convert,
93 => KeyCode::KanaMode,
94 => KeyCode::NonConvert,
// 95 => KeyCode::KPJPCOMMA,
96 => KeyCode::NumpadEnter,
97 => KeyCode::ControlRight,
98 => KeyCode::NumpadDivide,
99 => KeyCode::PrintScreen,
100 => KeyCode::AltRight,
// 101 => KeyCode::LINEFEED,
102 => KeyCode::Home,
103 => KeyCode::ArrowUp,
104 => KeyCode::PageUp,
105 => KeyCode::ArrowLeft,
106 => KeyCode::ArrowRight,
107 => KeyCode::End,
108 => KeyCode::ArrowDown,
109 => KeyCode::PageDown,
110 => KeyCode::Insert,
111 => KeyCode::Delete,
// 112 => KeyCode::MACRO,
113 => KeyCode::AudioVolumeMute,
114 => KeyCode::AudioVolumeDown,
115 => KeyCode::AudioVolumeUp,
// 116 => KeyCode::POWER,
117 => KeyCode::NumpadEqual,
// 118 => KeyCode::KPPLUSMINUS,
119 => KeyCode::Pause,
// 120 => KeyCode::SCALE,
121 => KeyCode::NumpadComma,
122 => KeyCode::Lang1,
123 => KeyCode::Lang2,
124 => KeyCode::IntlYen,
125 => KeyCode::SuperLeft,
126 => KeyCode::SuperRight,
127 => KeyCode::ContextMenu,
// 128 => KeyCode::STOP,
// 129 => KeyCode::AGAIN,
// 130 => KeyCode::PROPS,
// 131 => KeyCode::UNDO,
// 132 => KeyCode::FRONT,
// 133 => KeyCode::COPY,
// 134 => KeyCode::OPEN,
// 135 => KeyCode::PASTE,
// 136 => KeyCode::FIND,
// 137 => KeyCode::CUT,
// 138 => KeyCode::HELP,
// 139 => KeyCode::MENU,
// 140 => KeyCode::CALC,
// 141 => KeyCode::SETUP,
// 142 => KeyCode::SLEEP,
// 143 => KeyCode::WAKEUP,
// 144 => KeyCode::FILE,
// 145 => KeyCode::SENDFILE,
// 146 => KeyCode::DELETEFILE,
// 147 => KeyCode::XFER,
// 148 => KeyCode::PROG1,
// 149 => KeyCode::PROG2,
// 150 => KeyCode::WWW,
// 151 => KeyCode::MSDOS,
// 152 => KeyCode::COFFEE,
// 153 => KeyCode::ROTATE_DISPLAY,
// 154 => KeyCode::CYCLEWINDOWS,
// 155 => KeyCode::MAIL,
// 156 => KeyCode::BOOKMARKS,
// 157 => KeyCode::COMPUTER,
// 158 => KeyCode::BACK,
// 159 => KeyCode::FORWARD,
// 160 => KeyCode::CLOSECD,
// 161 => KeyCode::EJECTCD,
// 162 => KeyCode::EJECTCLOSECD,
163 => KeyCode::MediaTrackNext,
164 => KeyCode::MediaPlayPause,
165 => KeyCode::MediaTrackPrevious,
166 => KeyCode::MediaStop,
// 167 => KeyCode::RECORD,
// 168 => KeyCode::REWIND,
// 169 => KeyCode::PHONE,
// 170 => KeyCode::ISO,
// 171 => KeyCode::CONFIG,
// 172 => KeyCode::HOMEPAGE,
// 173 => KeyCode::REFRESH,
// 174 => KeyCode::EXIT,
// 175 => KeyCode::MOVE,
// 176 => KeyCode::EDIT,
// 177 => KeyCode::SCROLLUP,
// 178 => KeyCode::SCROLLDOWN,
// 179 => KeyCode::KPLEFTPAREN,
// 180 => KeyCode::KPRIGHTPAREN,
// 181 => KeyCode::NEW,
// 182 => KeyCode::REDO,
183 => KeyCode::F13,
184 => KeyCode::F14,
185 => KeyCode::F15,
186 => KeyCode::F16,
187 => KeyCode::F17,
188 => KeyCode::F18,
189 => KeyCode::F19,
190 => KeyCode::F20,
191 => KeyCode::F21,
192 => KeyCode::F22,
193 => KeyCode::F23,
194 => KeyCode::F24,
// 200 => KeyCode::PLAYCD,
// 201 => KeyCode::PAUSECD,
// 202 => KeyCode::PROG3,
// 203 => KeyCode::PROG4,
// 204 => KeyCode::DASHBOARD,
// 205 => KeyCode::SUSPEND,
// 206 => KeyCode::CLOSE,
// 207 => KeyCode::PLAY,
// 208 => KeyCode::FASTFORWARD,
// 209 => KeyCode::BASSBOOST,
// 210 => KeyCode::PRINT,
// 211 => KeyCode::HP,
// 212 => KeyCode::CAMERA,
// 213 => KeyCode::SOUND,
// 214 => KeyCode::QUESTION,
// 215 => KeyCode::EMAIL,
// 216 => KeyCode::CHAT,
// 217 => KeyCode::SEARCH,
// 218 => KeyCode::CONNECT,
// 219 => KeyCode::FINANCE,
// 220 => KeyCode::SPORT,
// 221 => KeyCode::SHOP,
// 222 => KeyCode::ALTERASE,
// 223 => KeyCode::CANCEL,
// 224 => KeyCode::BRIGHTNESSDOW,
// 225 => KeyCode::BRIGHTNESSU,
// 226 => KeyCode::MEDIA,
// 227 => KeyCode::SWITCHVIDEOMODE,
// 228 => KeyCode::KBDILLUMTOGGLE,
// 229 => KeyCode::KBDILLUMDOWN,
// 230 => KeyCode::KBDILLUMUP,
// 231 => KeyCode::SEND,
// 232 => KeyCode::REPLY,
// 233 => KeyCode::FORWARDMAIL,
// 234 => KeyCode::SAVE,
// 235 => KeyCode::DOCUMENTS,
// 236 => KeyCode::BATTERY,
// 237 => KeyCode::BLUETOOTH,
// 238 => KeyCode::WLAN,
// 239 => KeyCode::UWB,
240 => KeyCode::Unidentified(NativeKeyCode::Unidentified),
// 241 => KeyCode::VIDEO_NEXT,
// 242 => KeyCode::VIDEO_PREV,
// 243 => KeyCode::BRIGHTNESS_CYCLE,
// 244 => KeyCode::BRIGHTNESS_AUTO,
// 245 => KeyCode::DISPLAY_OFF,
// 246 => KeyCode::WWAN,
// 247 => KeyCode::RFKILL,
// 248 => KeyCode::KEY_MICMUTE,
_ => KeyCode::Unidentified(NativeKeyCode::Xkb(scancode)),
}
}
pub fn keycode_to_scancode(keycode: KeyCode) -> Option<u32> {
match keycode {
KeyCode::Unidentified(NativeKeyCode::Unidentified) => Some(240),
KeyCode::Unidentified(NativeKeyCode::Xkb(raw)) => Some(raw),
KeyCode::Escape => Some(1),
KeyCode::Digit1 => Some(2),
KeyCode::Digit2 => Some(3),
KeyCode::Digit3 => Some(4),
KeyCode::Digit4 => Some(5),
KeyCode::Digit5 => Some(6),
KeyCode::Digit6 => Some(7),
KeyCode::Digit7 => Some(8),
KeyCode::Digit8 => Some(9),
KeyCode::Digit9 => Some(10),
KeyCode::Digit0 => Some(11),
KeyCode::Minus => Some(12),
KeyCode::Equal => Some(13),
KeyCode::Backspace => Some(14),
KeyCode::Tab => Some(15),
KeyCode::KeyQ => Some(16),
KeyCode::KeyW => Some(17),
KeyCode::KeyE => Some(18),
KeyCode::KeyR => Some(19),
KeyCode::KeyT => Some(20),
KeyCode::KeyY => Some(21),
KeyCode::KeyU => Some(22),
KeyCode::KeyI => Some(23),
KeyCode::KeyO => Some(24),
KeyCode::KeyP => Some(25),
KeyCode::BracketLeft => Some(26),
KeyCode::BracketRight => Some(27),
KeyCode::Enter => Some(28),
KeyCode::ControlLeft => Some(29),
KeyCode::KeyA => Some(30),
KeyCode::KeyS => Some(31),
KeyCode::KeyD => Some(32),
KeyCode::KeyF => Some(33),
KeyCode::KeyG => Some(34),
KeyCode::KeyH => Some(35),
KeyCode::KeyJ => Some(36),
KeyCode::KeyK => Some(37),
KeyCode::KeyL => Some(38),
KeyCode::Semicolon => Some(39),
KeyCode::Quote => Some(40),
KeyCode::Backquote => Some(41),
KeyCode::ShiftLeft => Some(42),
KeyCode::Backslash => Some(43),
KeyCode::KeyZ => Some(44),
KeyCode::KeyX => Some(45),
KeyCode::KeyC => Some(46),
KeyCode::KeyV => Some(47),
KeyCode::KeyB => Some(48),
KeyCode::KeyN => Some(49),
KeyCode::KeyM => Some(50),
KeyCode::Comma => Some(51),
KeyCode::Period => Some(52),
KeyCode::Slash => Some(53),
KeyCode::ShiftRight => Some(54),
KeyCode::NumpadMultiply => Some(55),
KeyCode::AltLeft => Some(56),
KeyCode::Space => Some(57),
KeyCode::CapsLock => Some(58),
KeyCode::F1 => Some(59),
KeyCode::F2 => Some(60),
KeyCode::F3 => Some(61),
KeyCode::F4 => Some(62),
KeyCode::F5 => Some(63),
KeyCode::F6 => Some(64),
KeyCode::F7 => Some(65),
KeyCode::F8 => Some(66),
KeyCode::F9 => Some(67),
KeyCode::F10 => Some(68),
KeyCode::NumLock => Some(69),
KeyCode::ScrollLock => Some(70),
KeyCode::Numpad7 => Some(71),
KeyCode::Numpad8 => Some(72),
KeyCode::Numpad9 => Some(73),
KeyCode::NumpadSubtract => Some(74),
KeyCode::Numpad4 => Some(75),
KeyCode::Numpad5 => Some(76),
KeyCode::Numpad6 => Some(77),
KeyCode::NumpadAdd => Some(78),
KeyCode::Numpad1 => Some(79),
KeyCode::Numpad2 => Some(80),
KeyCode::Numpad3 => Some(81),
KeyCode::Numpad0 => Some(82),
KeyCode::NumpadDecimal => Some(83),
KeyCode::Lang5 => Some(85),
KeyCode::IntlBackslash => Some(86),
KeyCode::F11 => Some(87),
KeyCode::F12 => Some(88),
KeyCode::IntlRo => Some(89),
KeyCode::Lang3 => Some(90),
KeyCode::Lang4 => Some(91),
KeyCode::Convert => Some(92),
KeyCode::KanaMode => Some(93),
KeyCode::NonConvert => Some(94),
KeyCode::NumpadEnter => Some(96),
KeyCode::ControlRight => Some(97),
KeyCode::NumpadDivide => Some(98),
KeyCode::PrintScreen => Some(99),
KeyCode::AltRight => Some(100),
KeyCode::Home => Some(102),
KeyCode::ArrowUp => Some(103),
KeyCode::PageUp => Some(104),
KeyCode::ArrowLeft => Some(105),
KeyCode::ArrowRight => Some(106),
KeyCode::End => Some(107),
KeyCode::ArrowDown => Some(108),
KeyCode::PageDown => Some(109),
KeyCode::Insert => Some(110),
KeyCode::Delete => Some(111),
KeyCode::AudioVolumeMute => Some(113),
KeyCode::AudioVolumeDown => Some(114),
KeyCode::AudioVolumeUp => Some(115),
KeyCode::NumpadEqual => Some(117),
KeyCode::Pause => Some(119),
KeyCode::NumpadComma => Some(121),
KeyCode::Lang1 => Some(122),
KeyCode::Lang2 => Some(123),
KeyCode::IntlYen => Some(124),
KeyCode::SuperLeft => Some(125),
KeyCode::SuperRight => Some(126),
KeyCode::ContextMenu => Some(127),
KeyCode::MediaTrackNext => Some(163),
KeyCode::MediaPlayPause => Some(164),
KeyCode::MediaTrackPrevious => Some(165),
KeyCode::MediaStop => Some(166),
KeyCode::F13 => Some(183),
KeyCode::F14 => Some(184),
KeyCode::F15 => Some(185),
KeyCode::F16 => Some(186),
KeyCode::F17 => Some(187),
KeyCode::F18 => Some(188),
KeyCode::F19 => Some(189),
KeyCode::F20 => Some(190),
KeyCode::F21 => Some(191),
KeyCode::F22 => Some(192),
KeyCode::F23 => Some(193),
KeyCode::F24 => Some(194),
_ => None,
}
}
pub fn keysym_to_key(keysym: u32) -> Key {
use xkbcommon_dl::keysyms;
match keysym {
// TTY function keys
keysyms::BackSpace => Key::Backspace,
keysyms::Tab => Key::Tab,
// keysyms::Linefeed => Key::Linefeed,
keysyms::Clear => Key::Clear,
keysyms::Return => Key::Enter,
keysyms::Pause => Key::Pause,
keysyms::Scroll_Lock => Key::ScrollLock,
keysyms::Sys_Req => Key::PrintScreen,
keysyms::Escape => Key::Escape,
keysyms::Delete => Key::Delete,
// IME keys
keysyms::Multi_key => Key::Compose,
keysyms::Codeinput => Key::CodeInput,
keysyms::SingleCandidate => Key::SingleCandidate,
keysyms::MultipleCandidate => Key::AllCandidates,
keysyms::PreviousCandidate => Key::PreviousCandidate,
// Japanese keys
keysyms::Kanji => Key::KanjiMode,
keysyms::Muhenkan => Key::NonConvert,
keysyms::Henkan_Mode => Key::Convert,
keysyms::Romaji => Key::Romaji,
keysyms::Hiragana => Key::Hiragana,
keysyms::Hiragana_Katakana => Key::HiraganaKatakana,
keysyms::Zenkaku => Key::Zenkaku,
keysyms::Hankaku => Key::Hankaku,
keysyms::Zenkaku_Hankaku => Key::ZenkakuHankaku,
// keysyms::Touroku => Key::Touroku,
// keysyms::Massyo => Key::Massyo,
keysyms::Kana_Lock => Key::KanaMode,
keysyms::Kana_Shift => Key::KanaMode,
keysyms::Eisu_Shift => Key::Alphanumeric,
keysyms::Eisu_toggle => Key::Alphanumeric,
// NOTE: The next three items are aliases for values we've already mapped.
// keysyms::Kanji_Bangou => Key::CodeInput,
// keysyms::Zen_Koho => Key::AllCandidates,
// keysyms::Mae_Koho => Key::PreviousCandidate,
// Cursor control & motion
keysyms::Home => Key::Home,
keysyms::Left => Key::ArrowLeft,
keysyms::Up => Key::ArrowUp,
keysyms::Right => Key::ArrowRight,
keysyms::Down => Key::ArrowDown,
// keysyms::Prior => Key::PageUp,
keysyms::Page_Up => Key::PageUp,
// keysyms::Next => Key::PageDown,
keysyms::Page_Down => Key::PageDown,
keysyms::End => Key::End,
// keysyms::Begin => Key::Begin,
// Misc. functions
keysyms::Select => Key::Select,
keysyms::Print => Key::PrintScreen,
keysyms::Execute => Key::Execute,
keysyms::Insert => Key::Insert,
keysyms::Undo => Key::Undo,
keysyms::Redo => Key::Redo,
keysyms::Menu => Key::ContextMenu,
keysyms::Find => Key::Find,
keysyms::Cancel => Key::Cancel,
keysyms::Help => Key::Help,
keysyms::Break => Key::Pause,
keysyms::Mode_switch => Key::ModeChange,
// keysyms::script_switch => Key::ModeChange,
keysyms::Num_Lock => Key::NumLock,
// Keypad keys
// keysyms::KP_Space => Key::Character(" "),
keysyms::KP_Tab => Key::Tab,
keysyms::KP_Enter => Key::Enter,
keysyms::KP_F1 => Key::F1,
keysyms::KP_F2 => Key::F2,
keysyms::KP_F3 => Key::F3,
keysyms::KP_F4 => Key::F4,
keysyms::KP_Home => Key::Home,
keysyms::KP_Left => Key::ArrowLeft,
keysyms::KP_Up => Key::ArrowLeft,
keysyms::KP_Right => Key::ArrowRight,
keysyms::KP_Down => Key::ArrowDown,
// keysyms::KP_Prior => Key::PageUp,
keysyms::KP_Page_Up => Key::PageUp,
// keysyms::KP_Next => Key::PageDown,
keysyms::KP_Page_Down => Key::PageDown,
keysyms::KP_End => Key::End,
// This is the key labeled "5" on the numpad when NumLock is off.
// keysyms::KP_Begin => Key::Begin,
keysyms::KP_Insert => Key::Insert,
keysyms::KP_Delete => Key::Delete,
// keysyms::KP_Equal => Key::Equal,
// keysyms::KP_Multiply => Key::Multiply,
// keysyms::KP_Add => Key::Add,
// keysyms::KP_Separator => Key::Separator,
// keysyms::KP_Subtract => Key::Subtract,
// keysyms::KP_Decimal => Key::Decimal,
// keysyms::KP_Divide => Key::Divide,
// keysyms::KP_0 => Key::Character("0"),
// keysyms::KP_1 => Key::Character("1"),
// keysyms::KP_2 => Key::Character("2"),
// keysyms::KP_3 => Key::Character("3"),
// keysyms::KP_4 => Key::Character("4"),
// keysyms::KP_5 => Key::Character("5"),
// keysyms::KP_6 => Key::Character("6"),
// keysyms::KP_7 => Key::Character("7"),
// keysyms::KP_8 => Key::Character("8"),
// keysyms::KP_9 => Key::Character("9"),
// Function keys
keysyms::F1 => Key::F1,
keysyms::F2 => Key::F2,
keysyms::F3 => Key::F3,
keysyms::F4 => Key::F4,
keysyms::F5 => Key::F5,
keysyms::F6 => Key::F6,
keysyms::F7 => Key::F7,
keysyms::F8 => Key::F8,
keysyms::F9 => Key::F9,
keysyms::F10 => Key::F10,
keysyms::F11 => Key::F11,
keysyms::F12 => Key::F12,
keysyms::F13 => Key::F13,
keysyms::F14 => Key::F14,
keysyms::F15 => Key::F15,
keysyms::F16 => Key::F16,
keysyms::F17 => Key::F17,
keysyms::F18 => Key::F18,
keysyms::F19 => Key::F19,
keysyms::F20 => Key::F20,
keysyms::F21 => Key::F21,
keysyms::F22 => Key::F22,
keysyms::F23 => Key::F23,
keysyms::F24 => Key::F24,
keysyms::F25 => Key::F25,
keysyms::F26 => Key::F26,
keysyms::F27 => Key::F27,
keysyms::F28 => Key::F28,
keysyms::F29 => Key::F29,
keysyms::F30 => Key::F30,
keysyms::F31 => Key::F31,
keysyms::F32 => Key::F32,
keysyms::F33 => Key::F33,
keysyms::F34 => Key::F34,
keysyms::F35 => Key::F35,
// Modifiers
keysyms::Shift_L => Key::Shift,
keysyms::Shift_R => Key::Shift,
keysyms::Control_L => Key::Control,
keysyms::Control_R => Key::Control,
keysyms::Caps_Lock => Key::CapsLock,
// keysyms::Shift_Lock => Key::ShiftLock,
// keysyms::Meta_L => Key::Meta,
// keysyms::Meta_R => Key::Meta,
keysyms::Alt_L => Key::Alt,
keysyms::Alt_R => Key::Alt,
keysyms::Super_L => Key::Super,
keysyms::Super_R => Key::Super,
keysyms::Hyper_L => Key::Hyper,
keysyms::Hyper_R => Key::Hyper,
// XKB function and modifier keys
// keysyms::ISO_Lock => Key::IsoLock,
// keysyms::ISO_Level2_Latch => Key::IsoLevel2Latch,
keysyms::ISO_Level3_Shift => Key::AltGraph,
keysyms::ISO_Level3_Latch => Key::AltGraph,
keysyms::ISO_Level3_Lock => Key::AltGraph,
// keysyms::ISO_Level5_Shift => Key::IsoLevel5Shift,
// keysyms::ISO_Level5_Latch => Key::IsoLevel5Latch,
// keysyms::ISO_Level5_Lock => Key::IsoLevel5Lock,
// keysyms::ISO_Group_Shift => Key::IsoGroupShift,
// keysyms::ISO_Group_Latch => Key::IsoGroupLatch,
// keysyms::ISO_Group_Lock => Key::IsoGroupLock,
keysyms::ISO_Next_Group => Key::GroupNext,
// keysyms::ISO_Next_Group_Lock => Key::GroupNextLock,
keysyms::ISO_Prev_Group => Key::GroupPrevious,
// keysyms::ISO_Prev_Group_Lock => Key::GroupPreviousLock,
keysyms::ISO_First_Group => Key::GroupFirst,
// keysyms::ISO_First_Group_Lock => Key::GroupFirstLock,
keysyms::ISO_Last_Group => Key::GroupLast,
// keysyms::ISO_Last_Group_Lock => Key::GroupLastLock,
//
keysyms::ISO_Left_Tab => Key::Tab,
// keysyms::ISO_Move_Line_Up => Key::IsoMoveLineUp,
// keysyms::ISO_Move_Line_Down => Key::IsoMoveLineDown,
// keysyms::ISO_Partial_Line_Up => Key::IsoPartialLineUp,
// keysyms::ISO_Partial_Line_Down => Key::IsoPartialLineDown,
// keysyms::ISO_Partial_Space_Left => Key::IsoPartialSpaceLeft,
// keysyms::ISO_Partial_Space_Right => Key::IsoPartialSpaceRight,
// keysyms::ISO_Set_Margin_Left => Key::IsoSetMarginLeft,
// keysyms::ISO_Set_Margin_Right => Key::IsoSetMarginRight,
// keysyms::ISO_Release_Margin_Left => Key::IsoReleaseMarginLeft,
// keysyms::ISO_Release_Margin_Right => Key::IsoReleaseMarginRight,
// keysyms::ISO_Release_Both_Margins => Key::IsoReleaseBothMargins,
// keysyms::ISO_Fast_Cursor_Left => Key::IsoFastCursorLeft,
// keysyms::ISO_Fast_Cursor_Right => Key::IsoFastCursorRight,
// keysyms::ISO_Fast_Cursor_Up => Key::IsoFastCursorUp,
// keysyms::ISO_Fast_Cursor_Down => Key::IsoFastCursorDown,
// keysyms::ISO_Continuous_Underline => Key::IsoContinuousUnderline,
// keysyms::ISO_Discontinuous_Underline => Key::IsoDiscontinuousUnderline,
// keysyms::ISO_Emphasize => Key::IsoEmphasize,
// keysyms::ISO_Center_Object => Key::IsoCenterObject,
keysyms::ISO_Enter => Key::Enter,
// dead_grave..dead_currency
// dead_lowline..dead_longsolidusoverlay
// dead_a..dead_capital_schwa
// dead_greek
// First_Virtual_Screen..Terminate_Server
// AccessX_Enable..AudibleBell_Enable
// Pointer_Left..Pointer_Drag5
// Pointer_EnableKeys..Pointer_DfltBtnPrev
// ch..C_H
// 3270 terminal keys
// keysyms::3270_Duplicate => Key::Duplicate,
// keysyms::3270_FieldMark => Key::FieldMark,
// keysyms::3270_Right2 => Key::Right2,
// keysyms::3270_Left2 => Key::Left2,
// keysyms::3270_BackTab => Key::BackTab,
keysyms::_3270_EraseEOF => Key::EraseEof,
// keysyms::3270_EraseInput => Key::EraseInput,
// keysyms::3270_Reset => Key::Reset,
// keysyms::3270_Quit => Key::Quit,
// keysyms::3270_PA1 => Key::Pa1,
// keysyms::3270_PA2 => Key::Pa2,
// keysyms::3270_PA3 => Key::Pa3,
// keysyms::3270_Test => Key::Test,
keysyms::_3270_Attn => Key::Attn,
// keysyms::3270_CursorBlink => Key::CursorBlink,
// keysyms::3270_AltCursor => Key::AltCursor,
// keysyms::3270_KeyClick => Key::KeyClick,
// keysyms::3270_Jump => Key::Jump,
// keysyms::3270_Ident => Key::Ident,
// keysyms::3270_Rule => Key::Rule,
// keysyms::3270_Copy => Key::Copy,
keysyms::_3270_Play => Key::Play,
// keysyms::3270_Setup => Key::Setup,
// keysyms::3270_Record => Key::Record,
// keysyms::3270_ChangeScreen => Key::ChangeScreen,
// keysyms::3270_DeleteWord => Key::DeleteWord,
keysyms::_3270_ExSelect => Key::ExSel,
keysyms::_3270_CursorSelect => Key::CrSel,
keysyms::_3270_PrintScreen => Key::PrintScreen,
keysyms::_3270_Enter => Key::Enter,
keysyms::space => Key::Space,
// exclam..Sinh_kunddaliya
// XFree86
// keysyms::XF86_ModeLock => Key::ModeLock,
// XFree86 - Backlight controls
keysyms::XF86_MonBrightnessUp => Key::BrightnessUp,
keysyms::XF86_MonBrightnessDown => Key::BrightnessDown,
// keysyms::XF86_KbdLightOnOff => Key::LightOnOff,
// keysyms::XF86_KbdBrightnessUp => Key::KeyboardBrightnessUp,
// keysyms::XF86_KbdBrightnessDown => Key::KeyboardBrightnessDown,
// XFree86 - "Internet"
keysyms::XF86_Standby => Key::Standby,
keysyms::XF86_AudioLowerVolume => Key::AudioVolumeDown,
keysyms::XF86_AudioRaiseVolume => Key::AudioVolumeUp,
keysyms::XF86_AudioPlay => Key::MediaPlay,
keysyms::XF86_AudioStop => Key::MediaStop,
keysyms::XF86_AudioPrev => Key::MediaTrackPrevious,
keysyms::XF86_AudioNext => Key::MediaTrackNext,
keysyms::XF86_HomePage => Key::BrowserHome,
keysyms::XF86_Mail => Key::LaunchMail,
// keysyms::XF86_Start => Key::Start,
keysyms::XF86_Search => Key::BrowserSearch,
keysyms::XF86_AudioRecord => Key::MediaRecord,
// XFree86 - PDA
keysyms::XF86_Calculator => Key::LaunchApplication2,
// keysyms::XF86_Memo => Key::Memo,
// keysyms::XF86_ToDoList => Key::ToDoList,
keysyms::XF86_Calendar => Key::LaunchCalendar,
keysyms::XF86_PowerDown => Key::Power,
// keysyms::XF86_ContrastAdjust => Key::AdjustContrast,
// keysyms::XF86_RockerUp => Key::RockerUp,
// keysyms::XF86_RockerDown => Key::RockerDown,
// keysyms::XF86_RockerEnter => Key::RockerEnter,
// XFree86 - More "Internet"
keysyms::XF86_Back => Key::BrowserBack,
keysyms::XF86_Forward => Key::BrowserForward,
// keysyms::XF86_Stop => Key::Stop,
keysyms::XF86_Refresh => Key::BrowserRefresh,
keysyms::XF86_PowerOff => Key::Power,
keysyms::XF86_WakeUp => Key::WakeUp,
keysyms::XF86_Eject => Key::Eject,
keysyms::XF86_ScreenSaver => Key::LaunchScreenSaver,
keysyms::XF86_WWW => Key::LaunchWebBrowser,
keysyms::XF86_Sleep => Key::Standby,
keysyms::XF86_Favorites => Key::BrowserFavorites,
keysyms::XF86_AudioPause => Key::MediaPause,
// keysyms::XF86_AudioMedia => Key::AudioMedia,
keysyms::XF86_MyComputer => Key::LaunchApplication1,
// keysyms::XF86_VendorHome => Key::VendorHome,
// keysyms::XF86_LightBulb => Key::LightBulb,
// keysyms::XF86_Shop => Key::BrowserShop,
// keysyms::XF86_History => Key::BrowserHistory,
// keysyms::XF86_OpenURL => Key::OpenUrl,
// keysyms::XF86_AddFavorite => Key::AddFavorite,
// keysyms::XF86_HotLinks => Key::HotLinks,
// keysyms::XF86_BrightnessAdjust => Key::BrightnessAdjust,
// keysyms::XF86_Finance => Key::BrowserFinance,
// keysyms::XF86_Community => Key::BrowserCommunity,
keysyms::XF86_AudioRewind => Key::MediaRewind,
// keysyms::XF86_BackForward => Key::???,
// XF86_Launch0..XF86_LaunchF
// XF86_ApplicationLeft..XF86_CD
keysyms::XF86_Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :)
// XF86_Clear
keysyms::XF86_Close => Key::Close,
keysyms::XF86_Copy => Key::Copy,
keysyms::XF86_Cut => Key::Cut,
// XF86_Display..XF86_Documents
keysyms::XF86_Excel => Key::LaunchSpreadsheet,
// XF86_Explorer..XF86iTouch
keysyms::XF86_LogOff => Key::LogOff,
// XF86_Market..XF86_MenuPB
keysyms::XF86_MySites => Key::BrowserFavorites,
keysyms::XF86_New => Key::New,
// XF86_News..XF86_OfficeHome
keysyms::XF86_Open => Key::Open,
// XF86_Option
keysyms::XF86_Paste => Key::Paste,
keysyms::XF86_Phone => Key::LaunchPhone,
// XF86_Q
keysyms::XF86_Reply => Key::MailReply,
keysyms::XF86_Reload => Key::BrowserRefresh,
// XF86_RotateWindows..XF86_RotationKB
keysyms::XF86_Save => Key::Save,
// XF86_ScrollUp..XF86_ScrollClick
keysyms::XF86_Send => Key::MailSend,
keysyms::XF86_Spell => Key::SpellCheck,
keysyms::XF86_SplitScreen => Key::SplitScreenToggle,
// XF86_Support..XF86_User2KB
keysyms::XF86_Video => Key::LaunchMediaPlayer,
// XF86_WheelButton
keysyms::XF86_Word => Key::LaunchWordProcessor,
// XF86_Xfer
keysyms::XF86_ZoomIn => Key::ZoomIn,
keysyms::XF86_ZoomOut => Key::ZoomOut,
// XF86_Away..XF86_Messenger
keysyms::XF86_WebCam => Key::LaunchWebCam,
keysyms::XF86_MailForward => Key::MailForward,
// XF86_Pictures
keysyms::XF86_Music => Key::LaunchMusicPlayer,
// XF86_Battery..XF86_UWB
//
keysyms::XF86_AudioForward => Key::MediaFastForward,
// XF86_AudioRepeat
keysyms::XF86_AudioRandomPlay => Key::RandomToggle,
keysyms::XF86_Subtitle => Key::Subtitle,
keysyms::XF86_AudioCycleTrack => Key::MediaAudioTrack,
// XF86_CycleAngle..XF86_Blue
//
keysyms::XF86_Suspend => Key::Standby,
keysyms::XF86_Hibernate => Key::Hibernate,
// XF86_TouchpadToggle..XF86_TouchpadOff
//
keysyms::XF86_AudioMute => Key::AudioVolumeMute,
// XF86_Switch_VT_1..XF86_Switch_VT_12
// XF86_Ungrab..XF86_ClearGrab
keysyms::XF86_Next_VMode => Key::VideoModeNext,
// keysyms::XF86_Prev_VMode => Key::VideoModePrevious,
// XF86_LogWindowTree..XF86_LogGrabInfo
// SunFA_Grave..SunFA_Cedilla
// keysyms::SunF36 => Key::F36 | Key::F11,
// keysyms::SunF37 => Key::F37 | Key::F12,
// keysyms::SunSys_Req => Key::PrintScreen,
// The next couple of xkb (until SunStop) are already handled.
// SunPrint_Screen..SunPageDown
// SunUndo..SunFront
keysyms::SUN_Copy => Key::Copy,
keysyms::SUN_Open => Key::Open,
keysyms::SUN_Paste => Key::Paste,
keysyms::SUN_Cut => Key::Cut,
// SunPowerSwitch
keysyms::SUN_AudioLowerVolume => Key::AudioVolumeDown,
keysyms::SUN_AudioMute => Key::AudioVolumeMute,
keysyms::SUN_AudioRaiseVolume => Key::AudioVolumeUp,
// SUN_VideoDegauss
keysyms::SUN_VideoLowerBrightness => Key::BrightnessDown,
keysyms::SUN_VideoRaiseBrightness => Key::BrightnessUp,
// SunPowerSwitchShift
//
0 => Key::Unidentified(NativeKey::Unidentified),
_ => Key::Unidentified(NativeKey::Xkb(keysym)),
}
}
pub fn keysym_location(keysym: u32) -> KeyLocation {
use xkbcommon_dl::keysyms;
match keysym {
keysyms::Shift_L
| keysyms::Control_L
| keysyms::Meta_L
| keysyms::Alt_L
| keysyms::Super_L
| keysyms::Hyper_L => KeyLocation::Left,
keysyms::Shift_R
| keysyms::Control_R
| keysyms::Meta_R
| keysyms::Alt_R
| keysyms::Super_R
| keysyms::Hyper_R => KeyLocation::Right,
keysyms::KP_0
| keysyms::KP_1
| keysyms::KP_2
| keysyms::KP_3
| keysyms::KP_4
| keysyms::KP_5
| keysyms::KP_6
| keysyms::KP_7
| keysyms::KP_8
| keysyms::KP_9
| keysyms::KP_Space
| keysyms::KP_Tab
| keysyms::KP_Enter
| keysyms::KP_F1
| keysyms::KP_F2
| keysyms::KP_F3
| keysyms::KP_F4
| keysyms::KP_Home
| keysyms::KP_Left
| keysyms::KP_Up
| keysyms::KP_Right
| keysyms::KP_Down
| keysyms::KP_Page_Up
| keysyms::KP_Page_Down
| keysyms::KP_End
| keysyms::KP_Begin
| keysyms::KP_Insert
| keysyms::KP_Delete
| keysyms::KP_Equal
| keysyms::KP_Multiply
| keysyms::KP_Add
| keysyms::KP_Separator
| keysyms::KP_Subtract
| keysyms::KP_Decimal
| keysyms::KP_Divide => KeyLocation::Numpad,
_ => KeyLocation::Standard,
}
}

View File

@@ -1,2 +0,0 @@
pub mod keymap;
pub mod xkb_state;

View File

@@ -1,677 +0,0 @@
use std::convert::TryInto;
use std::env;
use std::ffi::CString;
use std::os::raw::c_char;
use std::os::unix::ffi::OsStringExt;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use once_cell::sync::Lazy;
use smol_str::SmolStr;
use xkbcommon_dl::{
self as ffi, xkb_state_component, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon,
XkbCommonCompose,
};
#[cfg(feature = "wayland")]
use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd};
#[cfg(feature = "x11")]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
use crate::event::KeyEvent;
use crate::platform_impl::common::keymap;
use crate::platform_impl::KeyEventExtra;
use crate::{
event::ElementState,
keyboard::{Key, KeyCode, KeyLocation},
};
// TODO: Wire this up without using a static `AtomicBool`.
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
#[inline(always)]
pub fn reset_dead_keys() {
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
}
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
#[cfg(feature = "x11")]
static XKBXH: Lazy<&'static ffi::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
#[derive(Debug)]
pub struct KbdState {
#[cfg(feature = "x11")]
xcb_connection: *mut xcb_connection_t,
xkb_context: *mut ffi::xkb_context,
xkb_keymap: *mut ffi::xkb_keymap,
xkb_state: *mut ffi::xkb_state,
xkb_compose_table: *mut ffi::xkb_compose_table,
xkb_compose_state: *mut ffi::xkb_compose_state,
xkb_compose_state_2: *mut ffi::xkb_compose_state,
mods_state: ModifiersState,
#[cfg(feature = "x11")]
pub core_keyboard_id: i32,
scratch_buffer: Vec<u8>,
}
impl KbdState {
pub fn update_modifiers(
&mut self,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
depressed_group: u32,
latched_group: u32,
locked_group: u32,
) {
if !self.ready() {
return;
}
let mask = unsafe {
(XKBH.xkb_state_update_mask)(
self.xkb_state,
mods_depressed,
mods_latched,
mods_locked,
depressed_group,
latched_group,
locked_group,
)
};
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
// effective value of mods have changed, we need to update our state
self.mods_state.update_with(self.xkb_state);
}
}
pub fn get_one_sym_raw(&mut self, keycode: u32) -> u32 {
if !self.ready() {
return 0;
}
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) }
}
pub fn get_utf8_raw(&mut self, keycode: u32) -> Option<SmolStr> {
if !self.ready() {
return None;
}
let xkb_state = self.xkb_state;
self.make_string_with({
|ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(xkb_state, keycode, ptr, len) }
})
}
fn compose_feed_normal(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
self.compose_feed(self.xkb_compose_state, keysym)
}
fn compose_feed_2(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
self.compose_feed(self.xkb_compose_state_2, keysym)
}
fn compose_feed(
&mut self,
xkb_compose_state: *mut ffi::xkb_compose_state,
keysym: u32,
) -> Option<ffi::xkb_compose_feed_result> {
if !self.ready() || self.xkb_compose_state.is_null() {
return None;
}
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
unsafe { self.init_compose() };
}
Some(unsafe { (XKBCH.xkb_compose_state_feed)(xkb_compose_state, keysym) })
}
fn compose_status_normal(&mut self) -> Option<ffi::xkb_compose_status> {
self.compose_status(self.xkb_compose_state)
}
fn compose_status(
&mut self,
xkb_compose_state: *mut ffi::xkb_compose_state,
) -> Option<ffi::xkb_compose_status> {
if !self.ready() || xkb_compose_state.is_null() {
return None;
}
Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) })
}
fn compose_get_utf8_normal(&mut self) -> Option<SmolStr> {
self.compose_get_utf8(self.xkb_compose_state)
}
fn compose_get_utf8_2(&mut self) -> Option<SmolStr> {
self.compose_get_utf8(self.xkb_compose_state_2)
}
fn compose_get_utf8(
&mut self,
xkb_compose_state: *mut ffi::xkb_compose_state,
) -> Option<SmolStr> {
if !self.ready() || xkb_compose_state.is_null() {
return None;
}
self.make_string_with(|ptr, len| unsafe {
(XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len)
})
}
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
/// `xkb_state_key_get_utf8`.
fn make_string_with<F>(&mut self, mut f: F) -> Option<SmolStr>
where
F: FnMut(*mut c_char, usize) -> i32,
{
let size = f(ptr::null_mut(), 0);
if size == 0 {
return None;
}
let size = usize::try_from(size).unwrap();
self.scratch_buffer.clear();
// The allocated buffer must include space for the null-terminator
self.scratch_buffer.reserve(size + 1);
unsafe {
let written = f(
self.scratch_buffer.as_mut_ptr().cast(),
self.scratch_buffer.capacity(),
);
if usize::try_from(written).unwrap() != size {
// This will likely never happen
return None;
}
self.scratch_buffer.set_len(size);
};
byte_slice_to_smol_str(&self.scratch_buffer)
}
pub fn new() -> Result<Self, Error> {
if ffi::xkbcommon_option().is_none() {
return Err(Error::XKBNotFound);
}
let context =
unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
if context.is_null() {
return Err(Error::XKBNotFound);
}
let mut me = Self {
#[cfg(feature = "x11")]
xcb_connection: ptr::null_mut(),
xkb_context: context,
xkb_keymap: ptr::null_mut(),
xkb_state: ptr::null_mut(),
xkb_compose_table: ptr::null_mut(),
xkb_compose_state: ptr::null_mut(),
xkb_compose_state_2: ptr::null_mut(),
mods_state: ModifiersState::new(),
#[cfg(feature = "x11")]
core_keyboard_id: 0,
scratch_buffer: Vec::new(),
};
unsafe { me.init_compose() };
Ok(me)
}
#[cfg(feature = "x11")]
pub fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result<Self, Error> {
let mut me = Self::new()?;
me.xcb_connection = connection;
let result = unsafe {
(XKBXH.xkb_x11_setup_xkb_extension)(
connection,
1,
2,
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
assert_eq!(result, 1, "Failed to initialize libxkbcommon");
unsafe { me.init_with_x11_keymap() };
Ok(me)
}
unsafe fn init_compose(&mut self) {
let locale = env::var_os("LC_ALL")
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LC_CTYPE"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LANG"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.unwrap_or_else(|| "C".into());
let locale = CString::new(locale.into_vec()).unwrap();
let compose_table = (XKBCH.xkb_compose_table_new_from_locale)(
self.xkb_context,
locale.as_ptr(),
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
);
if compose_table.is_null() {
// init of compose table failed, continue without compose
return;
}
let compose_state = (XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
);
if compose_state.is_null() {
// init of compose state failed, continue without compose
(XKBCH.xkb_compose_table_unref)(compose_table);
return;
}
let compose_state_2 = (XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
);
if compose_state_2.is_null() {
// init of compose state failed, continue without compose
(XKBCH.xkb_compose_table_unref)(compose_table);
(XKBCH.xkb_compose_state_unref)(compose_state);
return;
}
self.xkb_compose_table = compose_table;
self.xkb_compose_state = compose_state;
self.xkb_compose_state_2 = compose_state_2;
}
unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) {
self.xkb_keymap = keymap;
self.xkb_state = state;
self.mods_state.update_with(state);
}
unsafe fn de_init(&mut self) {
(XKBH.xkb_state_unref)(self.xkb_state);
self.xkb_state = ptr::null_mut();
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
self.xkb_keymap = ptr::null_mut();
}
#[cfg(feature = "x11")]
pub unsafe fn init_with_x11_keymap(&mut self) {
if !self.xkb_keymap.is_null() {
self.de_init();
}
// TODO: Support keyboards other than the "virtual core keyboard device".
self.core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection);
let keymap = (XKBXH.xkb_x11_keymap_new_from_device)(
self.xkb_context,
self.xcb_connection,
self.core_keyboard_id,
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
if keymap.is_null() {
panic!("Failed to get keymap from X11 server.");
}
let state = (XKBXH.xkb_x11_state_new_from_device)(
keymap,
self.xcb_connection,
self.core_keyboard_id,
);
self.post_init(state, keymap);
}
#[cfg(feature = "wayland")]
pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) {
if !self.xkb_keymap.is_null() {
self.de_init();
}
let map = MmapOptions::new()
.len(size)
.map_copy_read_only(&fd)
.unwrap();
let keymap = (XKBH.xkb_keymap_new_from_string)(
self.xkb_context,
map.as_ptr() as *const _,
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
if keymap.is_null() {
panic!("Received invalid keymap from compositor.");
}
let state = (XKBH.xkb_state_new)(keymap);
self.post_init(state, keymap);
}
#[cfg(feature = "wayland")]
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 }
}
#[inline]
pub fn ready(&self) -> bool {
!self.xkb_state.is_null()
}
#[inline]
pub fn mods_state(&self) -> ModifiersState {
self.mods_state
}
pub fn process_key_event(
&mut self,
keycode: u32,
state: ElementState,
repeat: bool,
) -> KeyEvent {
let mut event =
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
let physical_key = event.keycode();
let (logical_key, location) = event.key();
let text = event.text();
let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers();
let platform_specific = KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
};
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
platform_specific,
}
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
self.scratch_buffer.clear();
self.scratch_buffer.reserve(8);
loop {
let bytes_written = unsafe {
(XKBH.xkb_keysym_to_utf8)(
keysym,
self.scratch_buffer.as_mut_ptr().cast(),
self.scratch_buffer.capacity(),
)
};
if bytes_written == 0 {
return None;
} else if bytes_written == -1 {
self.scratch_buffer.reserve(8);
} else {
unsafe {
self.scratch_buffer
.set_len(bytes_written.try_into().unwrap())
};
break;
}
}
// Remove the null-terminator
self.scratch_buffer.pop();
byte_slice_to_smol_str(&self.scratch_buffer)
}
}
impl Drop for KbdState {
fn drop(&mut self) {
unsafe {
if !self.xkb_compose_state.is_null() {
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state);
}
if !self.xkb_compose_state_2.is_null() {
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state_2);
}
if !self.xkb_compose_table.is_null() {
(XKBCH.xkb_compose_table_unref)(self.xkb_compose_table);
}
if !self.xkb_state.is_null() {
(XKBH.xkb_state_unref)(self.xkb_state);
}
if !self.xkb_keymap.is_null() {
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
}
(XKBH.xkb_context_unref)(self.xkb_context);
}
}
}
struct KeyEventResults<'a> {
state: &'a mut KbdState,
keycode: u32,
keysym: u32,
compose: Option<XkbCompose>,
}
impl<'a> KeyEventResults<'a> {
fn new(state: &'a mut KbdState, keycode: u32, compose: bool) -> Self {
let keysym = state.get_one_sym_raw(keycode);
let compose = if compose {
Some(match state.compose_feed_normal(keysym) {
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => {
// Unwrapping is safe here, as `compose_feed` returns `None` when composition is uninitialized.
XkbCompose::Accepted(state.compose_status_normal().unwrap())
}
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored,
None => XkbCompose::Uninitialized,
})
} else {
None
};
KeyEventResults {
state,
keycode,
keysym,
compose,
}
}
fn keycode(&mut self) -> KeyCode {
keymap::raw_keycode_to_keycode(self.keycode)
}
pub fn key(&mut self) -> (Key, KeyLocation) {
self.keysym_to_key(self.keysym)
.unwrap_or_else(|(key, location)| match self.compose {
Some(XkbCompose::Accepted(ffi::xkb_compose_status::XKB_COMPOSE_COMPOSING)) => {
// When pressing a dead key twice, the non-combining variant of that character will be
// produced. Since this function only concerns itself with a single keypress, we simulate
// this double press here by feeding the keysym to the compose state twice.
self.state.compose_feed_2(self.keysym);
match self.state.compose_feed_2(self.keysym) {
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => (
// Extracting only a single `char` here *should* be fine, assuming that no dead
// key's non-combining variant ever occupies more than one `char`.
Key::Dead(
self.state
.compose_get_utf8_2()
.map(|s| s.chars().next().unwrap()),
),
location,
),
_ => (key, location),
}
}
_ => (
self.composed_text()
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
.map(Key::Character)
.unwrap_or(key),
location,
),
})
}
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
let mut keysyms = ptr::null();
let keysym_count = unsafe {
let layout = (XKBH.xkb_state_key_get_layout)(self.state.xkb_state, self.keycode);
(XKBH.xkb_keymap_key_get_syms_by_level)(
self.state.xkb_keymap,
self.keycode,
layout,
// NOTE: The level should be zero to ignore modifiers.
0,
&mut keysyms,
)
};
let keysym = if keysym_count == 1 {
unsafe { *keysyms }
} else {
0
};
self.keysym_to_key(keysym)
.unwrap_or_else(|(key, location)| {
(
self.state
.keysym_to_utf8_raw(keysym)
.map(Key::Character)
.unwrap_or(key),
location,
)
})
}
fn keysym_to_key(&mut self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = super::keymap::keysym_location(keysym);
let key = super::keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) {
Err((key, location))
} else {
Ok((key, location))
}
}
pub fn text(&mut self) -> Option<SmolStr> {
self.composed_text()
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
}
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the oppsite behaviour.
self.composed_text()
.unwrap_or_else(|_| self.state.get_utf8_raw(self.keycode))
}
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
if let Some(compose) = &self.compose {
match compose {
XkbCompose::Accepted(status) => match status {
ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => {
Ok(self.state.compose_get_utf8_normal())
}
ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
_ => Ok(None),
},
XkbCompose::Ignored | XkbCompose::Uninitialized => Err(()),
}
} else {
Err(())
}
}
}
/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
///
/// For some modifiers, this means that the key is currently pressed, others are toggled
/// (like caps lock).
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ModifiersState {
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "shift" key
pub shift: bool,
/// The "Caps lock" key
pub caps_lock: bool,
/// The "logo" key
///
/// Also known as the "windows" key on most keyboards
pub logo: bool,
/// The "Num lock" key
pub num_lock: bool,
}
impl ModifiersState {
fn new() -> Self {
Self::default()
}
fn update_with(&mut self, state: *mut ffi::xkb_state) {
let mod_name_is_active = |mod_name: &[u8]| unsafe {
(XKBH.xkb_state_mod_name_is_active)(
state,
mod_name.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
};
self.ctrl = mod_name_is_active(ffi::XKB_MOD_NAME_CTRL);
self.alt = mod_name_is_active(ffi::XKB_MOD_NAME_ALT);
self.shift = mod_name_is_active(ffi::XKB_MOD_NAME_SHIFT);
self.caps_lock = mod_name_is_active(ffi::XKB_MOD_NAME_CAPS);
self.logo = mod_name_is_active(ffi::XKB_MOD_NAME_LOGO);
self.num_lock = mod_name_is_active(ffi::XKB_MOD_NAME_NUM);
}
}
impl From<ModifiersState> for crate::keyboard::ModifiersState {
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
let mut to_mods = crate::keyboard::ModifiersState::empty();
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
to_mods
}
}
#[derive(Debug)]
pub enum Error {
/// libxkbcommon is not available
XKBNotFound,
}
#[derive(Copy, Clone, Debug)]
enum XkbCompose {
Accepted(ffi::xkb_compose_status),
Ignored,
Uninitialized,
}
// Note: This is track_caller so we can have more informative line numbers when logging
#[track_caller]
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
std::str::from_utf8(bytes)
.map(SmolStr::new)
.map_err(|e| {
warn!(
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
bytes
)
})
.ok()
}

View File

@@ -1,52 +1,52 @@
#![cfg(free_unix)]
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
#[cfg(all(not(x11_platform), not(wayland_platform)))]
#[cfg(all(not(feature = "x11"), not(feature = "wayland")))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
use std::error::Error;
use std::{collections::VecDeque, env, fmt};
#[cfg(x11_platform)]
use std::{
ffi::CStr,
mem::MaybeUninit,
os::raw::*,
sync::{Arc, Mutex},
};
#[cfg(feature = "x11")]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
use once_cell::sync::Lazy;
#[cfg(feature = "x11")]
use parking_lot::Mutex;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use smol_str::SmolStr;
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub use self::x11::XNotSupported;
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
#[cfg(feature = "x11")]
use crate::platform::unix::XlibErrorHook;
#[cfg(feature = "wayland")]
use crate::window::Theme;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, KeyEvent},
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon,
keyboard::{Key, KeyCode},
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
event::Event,
event_loop::{
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
pub mod common;
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
pub mod wayland;
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
@@ -60,9 +60,9 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
X,
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland,
}
@@ -87,67 +87,79 @@ impl ApplicationName {
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub name: Option<ApplicationName>,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub visual_infos: Option<XVisualInfo>,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub screen_id: Option<i32>,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub resize_increments: Option<Size>,
#[cfg(feature = "x11")]
pub base_size: Option<Size>,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub override_redirect: bool,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub x11_window_types: Vec<XWindowType>,
#[cfg(feature = "x11")]
pub gtk_theme_variant: Option<String>,
#[cfg(feature = "wayland")]
pub csd_theme: Option<Theme>,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
name: None,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
visual_infos: None,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
screen_id: None,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
resize_increments: None,
#[cfg(feature = "x11")]
base_size: None,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
override_redirect: false,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
x11_window_types: vec![XWindowType::Normal],
#[cfg(feature = "x11")]
gtk_theme_variant: None,
#[cfg(feature = "wayland")]
csd_theme: None,
}
}
}
#[cfg(x11_platform)]
pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
#[cfg(feature = "x11")]
pub static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
#[derive(Debug, Clone)]
pub enum OsError {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
XError(XError),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
XMisc(&'static str),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
WaylandMisc(&'static str),
}
impl fmt::Display for OsError {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
OsError::XMisc(e) => _f.pad(e),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
OsError::WaylandMisc(e) => _f.pad(e),
}
}
}
pub(crate) enum Window {
#[cfg(x11_platform)]
pub enum Window {
#[cfg(feature = "x11")]
X(x11::Window),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland(wayland::Window),
}
@@ -174,26 +186,26 @@ impl WindowId {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
X(x11::DeviceId),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
return DeviceId::X(x11::DeviceId::dummy());
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
X(x11::MonitorHandle),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland(wayland::MonitorHandle),
}
@@ -209,17 +221,17 @@ pub enum MonitorHandle {
macro_rules! x11_or_wayland {
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => {
match $what {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
$enum::X($($c1)*) => $enum2::X($x),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
$enum::Wayland($($c1)*) => $enum2::Wayland($x),
}
};
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => {
match $what {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
$enum::X($($c1)*) => $x,
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
$enum::Wayland($($c1)*) => $x,
}
};
@@ -253,20 +265,20 @@ impl MonitorHandle {
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _)
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64)
}
#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoMode {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
X(x11::VideoMode),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland(wayland::VideoMode),
}
@@ -287,7 +299,7 @@ impl VideoMode {
}
#[inline]
pub fn monitor(&self) -> MonitorHandle {
pub fn monitor(&self) -> RootMonitorHandle {
x11_or_wayland!(match self; VideoMode(m) => m.monitor())
}
}
@@ -300,11 +312,11 @@ impl Window {
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
match *window_target {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref window_target) => {
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
}
@@ -314,9 +326,9 @@ impl Window {
#[inline]
pub fn id(&self) -> WindowId {
match self {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Self::Wayland(window) => window.id(),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
Self::X(window) => window.id(),
}
}
@@ -326,11 +338,6 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_title(title));
}
#[inline]
pub fn set_transparent(&self, transparent: bool) {
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
}
#[inline]
pub fn set_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
@@ -381,16 +388,6 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions))
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.resize_increments())
}
#[inline]
pub fn set_resize_increments(&self, increments: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments))
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
@@ -401,16 +398,6 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.is_resizable())
}
#[inline]
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons))
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
x11_or_wayland!(match self; Window(w) => w.enabled_buttons())
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
@@ -431,11 +418,6 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.drag_window())
}
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
@@ -443,7 +425,7 @@ impl Window {
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; Window(w) => w.scale_factor())
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
}
#[inline]
@@ -467,17 +449,12 @@ impl Window {
}
#[inline]
pub fn is_minimized(&self) -> Option<bool> {
x11_or_wayland!(match self; Window(w) => w.is_minimized())
}
#[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
x11_or_wayland!(match self; Window(w) => w.fullscreen())
}
#[inline]
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor))
}
@@ -492,11 +469,11 @@ impl Window {
}
#[inline]
pub fn set_window_level(&self, _level: WindowLevel) {
pub fn set_always_on_top(&self, _always_on_top: bool) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_level(_level),
#[cfg(wayland_platform)]
#[cfg(feature = "x11")]
Window::X(ref w) => w.set_always_on_top(_always_on_top),
#[cfg(feature = "wayland")]
Window::Wayland(_) => (),
}
}
@@ -504,21 +481,16 @@ impl Window {
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
match self {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Window::Wayland(_) => (),
}
}
#[inline]
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size))
}
#[inline]
pub fn reset_dead_keys(&self) {
common::xkb_state::reset_dead_keys()
pub fn set_ime_position(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
}
#[inline]
@@ -526,25 +498,20 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
}
#[inline]
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose))
}
#[inline]
pub fn focus_window(&self) {
match self {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
Window::X(ref w) => w.focus_window(),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Window::Wayland(_) => (),
}
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
match self {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
}
@@ -555,17 +522,21 @@ impl Window {
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
match self {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(current_monitor)
Some(RootMonitorHandle {
inner: current_monitor,
})
}
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(current_monitor)
Some(RootMonitorHandle {
inner: current_monitor,
})
}
}
}
@@ -573,13 +544,13 @@ impl Window {
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
Window::X(ref window) => window
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Window::Wayland(ref window) => window
.available_monitors()
.into_iter()
@@ -589,14 +560,16 @@ impl Window {
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
match self {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
Window::X(ref window) => {
let primary_monitor = MonitorHandle::X(window.primary_monitor());
Some(primary_monitor)
Some(RootMonitorHandle {
inner: primary_monitor,
})
}
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Window::Wayland(ref window) => window.primary_monitor(),
}
}
@@ -610,73 +583,23 @@ impl Window {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
}
#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
x11_or_wayland!(match self; Window(window) => window.set_theme(theme))
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
x11_or_wayland!(match self; Window(window) => window.theme())
}
#[inline]
pub fn has_focus(&self) -> bool {
x11_or_wayland!(match self; Window(window) => window.has_focus())
}
pub fn title(&self) -> String {
x11_or_wayland!(match self; Window(window) => window.title())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub key_without_modifiers: Key,
pub text_with_all_modifiers: Option<SmolStr>,
}
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifiers
.as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}
impl KeyCodeExtScancode for KeyCode {
fn from_scancode(scancode: u32) -> KeyCode {
common::keymap::scancode_to_keycode(scancode)
}
fn to_scancode(self) -> Option<u32> {
common::keymap::keycode_to_scancode(self)
}
}
/// Hooks for X11 errors.
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub(crate) static mut XLIB_ERROR_HOOKS: Lazy<Mutex<Vec<XlibErrorHook>>> =
Lazy::new(|| Mutex::new(Vec::new()));
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap();
let xconn_lock = X11_BACKEND.lock();
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
for hook in XLIB_ERROR_HOOKS.lock().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
@@ -701,25 +624,25 @@ unsafe extern "C" fn x_error_callback(
// Don't log error.
if !error_handled {
error!("X11 error: {:#?}", error);
// XXX only update the error, if it wasn't handled by any of the hooks.
*xconn.latest_error.lock().unwrap() = Some(error);
}
*xconn.latest_error.lock() = Some(error);
}
// Fun fact: this return value is completely ignored.
0
}
pub enum EventLoop<T: 'static> {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland(Box<wayland::EventLoop<T>>),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
X(x11::EventLoop<T>),
}
pub enum EventLoopProxy<T: 'static> {
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
X(x11::EventLoopProxy<T>),
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland(wayland::EventLoopProxy<T>),
}
@@ -740,13 +663,13 @@ impl<T: 'static> EventLoop<T> {
);
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
if attributes.forced_backend == Some(Backend::X) {
// TODO: Propagate
return EventLoop::new_x11_any_thread().unwrap();
}
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
if attributes.forced_backend == Some(Backend::Wayland) {
// TODO: Propagate
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
@@ -756,55 +679,57 @@ impl<T: 'static> EventLoop<T> {
match env_var.as_str() {
"x11" => {
// TODO: propagate
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
return EventLoop::new_x11_any_thread()
.expect("Failed to initialize X11 backend");
#[cfg(not(x11_platform))]
#[cfg(not(feature = "x11"))]
panic!("x11 feature is not enabled")
}
"wayland" => {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
return EventLoop::new_wayland_any_thread()
.expect("Failed to initialize Wayland backend");
#[cfg(not(wayland_platform))]
#[cfg(not(feature = "wayland"))]
panic!("wayland feature is not enabled");
}
_ => panic!(
"Unknown environment variable value for {BACKEND_PREFERENCE_ENV_VAR}, try one of `x11`,`wayland`",
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
BACKEND_PREFERENCE_ENV_VAR,
),
}
}
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
let wayland_err = match EventLoop::new_wayland_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
let x11_err = match EventLoop::new_x11_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(not(wayland_platform))]
#[cfg(not(feature = "wayland"))]
let wayland_err = "backend disabled";
#[cfg(not(x11_platform))]
#[cfg(not(feature = "x11"))]
let x11_err = "backend disabled";
panic!(
"Failed to initialize any backend! Wayland status: {wayland_err:?} X11 status: {x11_err:?}",
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
wayland_err, x11_err,
);
}
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
let xconn = match X11_BACKEND.lock().as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(err.clone()),
};
@@ -842,9 +767,9 @@ impl<T: 'static> EventLoopProxy<T> {
}
pub enum EventLoopWindowTarget<T> {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
Wayland(wayland::EventLoopWindowTarget<T>),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
X(x11::EventLoopWindowTarget<T>),
}
@@ -852,9 +777,9 @@ impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(_) => true,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
_ => false,
}
}
@@ -862,13 +787,13 @@ impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match *self {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(ref evlp) => evlp
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref evlp) => evlp
.x_connection()
.available_monitors()
@@ -879,25 +804,27 @@ impl<T> EventLoopWindowTarget<T> {
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
match *self {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref evlp) => {
let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
Some(primary_monitor)
Some(RootMonitorHandle {
inner: primary_monitor,
})
}
}
}
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
match *self {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(_) => (),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp.set_listen_device_events(_allowed),
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter),
}
}

View File

@@ -0,0 +1,166 @@
//! SCTK environment setup.
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
use sctk::reexports::client::protocol::wl_shell::WlShell;
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
use sctk::reexports::client::{Attached, DispatchData};
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::{Environment, SimpleGlobal};
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
use sctk::shell::{Shell, ShellHandler, ShellHandling};
use sctk::shm::ShmHandler;
/// Set of extra features that are supported by the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
pointer_constraints: bool,
xdg_activation: bool,
}
impl WindowingFeatures {
/// Create `WindowingFeatures` based on the presented interfaces.
pub fn new(env: &Environment<WinitEnv>) -> Self {
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
pointer_constraints,
xdg_activation,
}
}
pub fn pointer_constraints(&self) -> bool {
self.pointer_constraints
}
pub fn xdg_activation(&self) -> bool {
self.xdg_activation
}
}
sctk::environment!(WinitEnv,
singles = [
WlShm => shm,
WlCompositor => compositor,
WlSubcompositor => subcompositor,
WlShell => shell,
XdgWmBase => shell,
ZxdgShellV6 => shell,
ZxdgDecorationManagerV1 => decoration_manager,
ZwpRelativePointerManagerV1 => relative_pointer_manager,
ZwpPointerConstraintsV1 => pointer_constraints,
ZwpTextInputManagerV3 => text_input_manager,
XdgActivationV1 => xdg_activation,
],
multis = [
WlSeat => seats,
WlOutput => outputs,
]
);
/// The environment that we utilize.
pub struct WinitEnv {
seats: SeatHandler,
outputs: OutputHandler,
shm: ShmHandler,
compositor: SimpleGlobal<WlCompositor>,
subcompositor: SimpleGlobal<WlSubcompositor>,
shell: ShellHandler,
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
xdg_activation: SimpleGlobal<XdgActivationV1>,
}
impl WinitEnv {
pub fn new() -> Self {
// Output tracking for available_monitors, etc.
let outputs = OutputHandler::new();
// Keyboard/Pointer/Touch input.
let seats = SeatHandler::new();
// Essential globals.
let shm = ShmHandler::new();
let compositor = SimpleGlobal::new();
let subcompositor = SimpleGlobal::new();
// Gracefully handle shell picking, since SCTK automatically supports multiple
// backends.
let shell = ShellHandler::new();
// Server side decorations.
let decoration_manager = SimpleGlobal::new();
// Device events for pointer.
let relative_pointer_manager = SimpleGlobal::new();
// Pointer grab functionality.
let pointer_constraints = SimpleGlobal::new();
// IME handling.
let text_input_manager = SimpleGlobal::new();
// Surface activation.
let xdg_activation = SimpleGlobal::new();
Self {
seats,
outputs,
shm,
compositor,
subcompositor,
shell,
decoration_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
xdg_activation,
}
}
}
impl ShellHandling for WinitEnv {
fn get_shell(&self) -> Option<Shell> {
self.shell.get_shell()
}
}
impl SeatHandling for WinitEnv {
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> SeatListener {
self.seats.listen(f)
}
}
impl OutputHandling for WinitEnv {
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> OutputStatusListener {
self.outputs.listen(f)
}
}

View File

@@ -1,93 +1,158 @@
//! The event-loop routines.
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::process;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
use sctk::reexports::calloop;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use sctk::environment::Environment;
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
use sctk::WaylandSource;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
mod proxy;
pub mod sink;
pub use proxy::EventLoopProxy;
use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState};
use super::env::{WindowingFeatures, WinitEnv};
use super::output::OutputManager;
use super::seat::SeatManager;
use super::window::shim::{self, WindowCompositorUpdate, WindowUserRequest};
use super::{DeviceId, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
mod proxy;
mod sink;
mod state;
pub use proxy::EventLoopProxy;
pub use sink::EventSink;
pub use state::WinitState;
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
pub struct EventLoopWindowTarget<T> {
/// Wayland display.
pub display: Display,
/// Environment to handle object creation, etc.
pub env: Environment<WinitEnv>,
/// Event loop handle.
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
/// Output manager.
pub output_manager: OutputManager,
/// State that we share across callbacks.
pub state: RefCell<WinitState>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// A proxy to wake up event loop.
pub event_loop_awakener: calloop::ping::Ping,
/// The available windowing features.
pub windowing_features: WindowingFeatures,
/// Theme manager to manage cursors.
///
/// It's being shared between all windows to avoid loading
/// multiple similar themes.
pub theme_manager: ThemeManager,
_marker: std::marker::PhantomData<T>,
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.display.get_display_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
}
/// The Wayland event loop.
pub struct EventLoop<T: 'static> {
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// Event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
/// Wayland display.
display: Display,
/// Pending user events.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
// we don't really want, since it'll break public API by a lot.
/// Pending events from the user.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// The Wayland dispatcher to has raw access to the queue when needed, such as
/// when creating a new window.
wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
connection: Connection,
/// Event loop window target.
/// Window target.
window_target: RootEventLoopWindowTarget<T>,
// XXX drop after everything else, just to be safe.
/// Calloop's event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
/// Output manager.
_seat_manager: SeatManager,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
let connection = Connection::connect_to_env()?;
// Connect to wayland server and setup event queue.
let display = Display::connect_to_env()?;
let mut event_queue = display.create_event_queue();
let display_proxy = display.attach(event_queue.token());
let (globals, mut event_queue) = globals::registry_queue_init(&connection)?;
let queue_handle = event_queue.handle();
// Setup environment.
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
let event_loop = calloop::EventLoop::<WinitState>::try_new()?;
// Create event loop.
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
// Build windowing features.
let windowing_features = WindowingFeatures::new(&env);
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
// Create a theme manager.
let compositor = env.require_global::<WlCompositor>();
let shm = env.require_global::<WlShm>();
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
// NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server.
event_queue.roundtrip(&mut winit_state)?;
// Setup theme seat and output managers.
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
let output_manager = OutputManager::new(&env);
// Register Wayland source.
let wayland_source = WaylandSource::new(event_queue)?;
// A source of events that we plug into our event loop.
let wayland_source = WaylandSource::new(event_queue);
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state)
queue.dispatch_pending(winit_state, |event, object, _| {
panic!(
"[calloop] Encountered an orphan event: {}@{} : {}",
event.interface,
object.as_ref().id(),
event.name
);
})
});
event_loop
let _wayland_source_dispatcher = event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
// Setup the user proxy.
// A source of user events.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
// User events channel.
event_loop
.handle()
.insert_source(user_events_channel, move |event, _, _| {
@@ -98,30 +163,52 @@ impl<T: 'static> EventLoop<T> {
// An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
// Handler of window requests.
event_loop
.handle()
.insert_source(event_loop_awakener_source, move |_, _, _| {
// No extra handling is required, we just need to wake-up.
.insert_source(event_loop_awakener_source, move |_, _, state| {
// Drain events here as well to account for application doing batch event processing
// on RedrawEventsCleared.
shim::handle_window_requests(state);
})?;
let window_target = EventLoopWindowTarget {
connection: connection.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
let event_loop_handle = event_loop.handle();
let window_map = HashMap::new();
let event_sink = EventSink::new();
let window_user_requests = HashMap::new();
let window_compositor_updates = HashMap::new();
// Create event loop window target.
let event_loop_window_target = EventLoopWindowTarget {
display: display.clone(),
env,
state: RefCell::new(WinitState {
window_map,
event_sink,
window_user_requests,
window_compositor_updates,
}),
event_loop_handle,
output_manager,
event_loop_awakener,
queue_handle,
state: RefCell::new(winit_state),
_marker: PhantomData,
wayland_dispatcher: wayland_dispatcher.clone(),
windowing_features,
theme_manager,
_marker: std::marker::PhantomData,
};
// Create event loop itself.
let event_loop = Self {
connection,
wayland_dispatcher,
user_events_sender,
pending_user_events,
event_loop,
display,
pending_user_events,
wayland_dispatcher,
_seat_manager: seat_manager,
user_events_sender,
window_target: RootEventLoopWindowTarget {
p: PlatformEventLoopWindowTarget::Wayland(window_target),
_marker: PhantomData,
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
_marker: std::marker::PhantomData,
},
};
@@ -141,11 +228,7 @@ impl<T: 'static> EventLoop<T> {
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::Poll;
// XXX preallocate certian structures to avoid allocating on each loop iteration.
let mut window_ids = Vec::<WindowId>::new();
let mut compositor_updates = Vec::<WindowCompositorUpdate>::new();
let mut buffer_sink = EventSink::new();
let pending_user_events = self.pending_user_events.clone();
callback(
Event::NewEvents(StartCause::Init),
@@ -153,19 +236,23 @@ impl<T: 'static> EventLoop<T> {
&mut control_flow,
);
// XXX For consistency all platforms must emit a 'Resumed' event even though Wayland
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
callback(Event::Resumed, &self.window_target, &mut control_flow);
// XXX We break on errors from dispatches, since if we've got protocol error
let mut window_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new();
let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = Vec::new();
let mut event_sink_back_buffer = Vec::new();
// NOTE We break on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = loop {
// Flush the connection.
let _ = self.connection.flush();
// Send pending events to the server.
let _ = self.display.flush();
// During the run of the user callback, some other code monitoring and reading the
// Wayland socket may have been run (mesa for example does this with vsync), if that
@@ -181,16 +268,13 @@ impl<T: 'static> EventLoop<T> {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
_ => unreachable!(),
};
match queue.dispatch_pending(state) {
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
Ok(dispatched) => dispatched > 0,
Err(error) => {
error!("Error dispatching wayland queue: {}", error);
break 1;
}
Err(error) => break error.raw_os_error().unwrap_or(1),
}
};
@@ -198,7 +282,7 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::ZERO;
let timeout = Duration::from_millis(0);
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
}
@@ -211,7 +295,7 @@ impl<T: 'static> EventLoop<T> {
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::ZERO)
Some(Duration::from_millis(0))
} else {
None
};
@@ -236,7 +320,7 @@ impl<T: 'static> EventLoop<T> {
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::ZERO
Duration::from_millis(0)
};
if let Err(error) = self.loop_dispatch(Some(duration)) {
@@ -269,7 +353,7 @@ impl<T: 'static> EventLoop<T> {
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in self.pending_user_events.borrow_mut().drain(..) {
for user_event in pending_user_events.borrow_mut().drain(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
&self.window_target,
@@ -278,30 +362,34 @@ impl<T: 'static> EventLoop<T> {
);
}
// Drain the pending compositor updates.
// Process 'new' pending updates from compositor.
self.with_state(|state| {
compositor_updates.append(&mut state.window_compositor_updates)
window_compositor_updates.clear();
window_compositor_updates.extend(
state
.window_compositor_updates
.iter_mut()
.map(|(wid, window_update)| (*wid, mem::take(window_update))),
);
});
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if let Some(scale_factor) = compositor_update.scale_factor {
for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() {
if let Some(scale_factor) = window_compositor_update.scale_factor.map(|f| f as f64)
{
let mut physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
let window_handle = state.window_map.get(window_id).unwrap();
let mut size = window_handle.size.lock().unwrap();
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
// Update the new logical size if it was changed.
let window_size = window_compositor_update.size.unwrap_or(*size);
*size = window_size;
window_size.to_physical(scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
@@ -312,58 +400,65 @@ impl<T: 'static> EventLoop<T> {
&mut callback,
);
// We don't update size on a window handle since we'll do that later
// when handling size update.
let new_logical_size = physical_size.to_logical(scale_factor);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.resize(new_logical_size);
});
}
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
window_compositor_update.size = Some(new_logical_size);
}
if let Some(size) = compositor_update.size.take() {
if let Some(size) = window_compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let window_handle = state.window_map.get_mut(window_id).unwrap();
let mut window_size = window_handle.size.lock().unwrap();
let scale_factor = window.scale_factor();
let physical_size = logical_to_physical_rounded(size, scale_factor);
// Always issue resize event on scale factor change.
let physical_size = if window_compositor_update.scale_factor.is_none()
&& *window_size == size
{
// The size hasn't changed, don't inform downstream about that.
None
} else {
*window_size = size;
let scale_factor =
sctk::get_surface_scale_factor(window_handle.window.surface());
let physical_size = size.to_physical(scale_factor as f64);
Some(physical_size)
};
// TODO could probably bring back size reporting optimization.
// We still perform all of those resize related logic even if the size
// hasn't changed, since GNOME relies on `set_geometry` calls after
// configures.
window_handle.window.resize(size.width, size.height);
window_handle.window.refresh();
// Mark the window as needed a redraw.
// Mark that refresh isn't required, since we've done it right now.
state
.window_requests
.get_mut()
.get_mut(&window_id)
.window_user_requests
.get_mut(window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
.refresh_frame = false;
physical_size
});
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
if let Some(physical_size) = physical_size {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
if compositor_update.close_window {
// If the close is requested, send it here.
if window_compositor_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
@@ -373,20 +468,18 @@ impl<T: 'static> EventLoop<T> {
}
}
// Push the events directly from the window.
// The purpose of the back buffer and that swap is to not hold borrow_mut when
// we're doing callback to the user, since we can double borrow if the user decides
// to create a window in one of those callbacks.
self.with_state(|state| {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
std::mem::swap(
&mut event_sink_back_buffer,
&mut state.event_sink.window_events,
)
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Handle non-synthetic events.
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
// Handle pending window events.
for event in event_sink_back_buffer.drain(..) {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
@@ -399,41 +492,42 @@ impl<T: 'static> EventLoop<T> {
&mut callback,
);
// Collect the window ids
// Apply user requests, so every event required resize and latter surface commit will
// be applied right before drawing. This will also ensure that every `RedrawRequested`
// event will be delivered in time.
self.with_state(|state| {
window_ids.extend(state.window_requests.get_mut().keys());
shim::handle_window_requests(state);
});
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
false
} else {
let mut redraw_requested = window_requests
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Process 'new' pending updates from compositor.
self.with_state(|state| {
window_user_requests.clear();
window_user_requests.extend(
state
.window_user_requests
.iter_mut()
.map(|(wid, window_request)| (*wid, mem::take(window_request))),
);
});
// Redraw the frames while at it.
redraw_requested |= state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap()
.refresh_frame();
// Handle RedrawRequested events.
for (window_id, mut window_request) in window_user_requests.iter() {
// Handle refresh of the frame.
if window_request.refresh_frame {
self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
window_handle.window.refresh();
});
redraw_requested
}
});
// In general refreshing the frame requires surface commit, those force user
// to redraw.
window_request.redraw_requested = true;
}
if request_redraw {
// Handle redraw request.
if window_request.redraw_requested {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(window_id)),
Event::RedrawRequested(crate::window::WindowId(*window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
@@ -464,14 +558,14 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U {
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
_ => unreachable!(),
};
callback(state)
f(state)
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
@@ -481,44 +575,8 @@ impl<T: 'static> EventLoop<T> {
_ => unreachable!(),
};
self.event_loop.dispatch(timeout, state).map_err(|error| {
error!("Error dispatching event loop: {}", error);
error.into()
})
self.event_loop
.dispatch(timeout, state)
.map_err(|error| error.into())
}
}
pub struct EventLoopWindowTarget<T> {
/// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping,
/// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>,
// TODO remove that RefCell once we can pass `&mut` in `Window::new`.
/// Winit state.
pub state: RefCell<WinitState>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
pub connection: Connection,
_marker: std::marker::PhantomData<T>,
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
}
// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -1,7 +1,5 @@
//! An event loop's sink to deliver events from the Wayland event callbacks.
use std::vec::Drain;
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
use crate::window::WindowId as RootWindowId;
@@ -21,7 +19,6 @@ impl EventSink {
}
/// Add new device event to a queue.
#[inline]
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
self.window_events.push(Event::DeviceEvent {
event,
@@ -30,21 +27,10 @@ impl EventSink {
}
/// Add new window event to a queue.
#[inline]
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(window_id),
});
}
#[inline]
pub fn append(&mut self, other: &mut Self) {
self.window_events.append(&mut other.window_events);
}
#[inline]
pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> {
self.window_events.drain(..)
}
}

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