mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2025-01-11 05:21:31 +11:00
Merge branch 'master' into merge-master-to-web
This commit is contained in:
commit
3e8669ea7f
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,4 +1,5 @@
|
||||||
- [ ] Tested on all platforms changed
|
- [ ] Tested on all platforms changed
|
||||||
|
- [ ] Compilation warnings were addressed
|
||||||
- [ ] `cargo fmt` has been run on this branch
|
- [ ] `cargo fmt` has been run on this branch
|
||||||
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
|
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
|
||||||
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
||||||
|
|
29
.travis.yml
29
.travis.yml
|
@ -34,7 +34,7 @@ matrix:
|
||||||
os: osx
|
os: osx
|
||||||
rust: stable
|
rust: stable
|
||||||
|
|
||||||
# iOS
|
# iOS x86_64
|
||||||
- env: TARGET=x86_64-apple-ios
|
- env: TARGET=x86_64-apple-ios
|
||||||
os: osx
|
os: osx
|
||||||
rust: nightly
|
rust: nightly
|
||||||
|
@ -42,18 +42,35 @@ matrix:
|
||||||
os: osx
|
os: osx
|
||||||
rust: stable
|
rust: stable
|
||||||
|
|
||||||
|
# iOS armv7
|
||||||
|
- env: TARGET=armv7-apple-ios
|
||||||
|
os: osx
|
||||||
|
rust: nightly
|
||||||
|
- env: TARGET=armv7-apple-ios
|
||||||
|
os: osx
|
||||||
|
rust: stable
|
||||||
|
|
||||||
|
# iOS arm64
|
||||||
|
- env: TARGET=aarch64-apple-ios
|
||||||
|
os: osx
|
||||||
|
rust: nightly
|
||||||
|
- env: TARGET=aarch64-apple-ios
|
||||||
|
os: osx
|
||||||
|
rust: stable
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- rustup self update
|
- rustup self update
|
||||||
- rustup target add $TARGET; true
|
- rustup target add $TARGET; true
|
||||||
- rustup component add rustfmt
|
- rustup toolchain install stable
|
||||||
|
- rustup component add rustfmt --toolchain stable
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo fmt --all -- --check
|
- cargo +stable fmt --all -- --check
|
||||||
- cargo build --target $TARGET --verbose
|
- cargo build --target $TARGET --verbose
|
||||||
- cargo build --target $TARGET --features serde --verbose
|
- cargo build --target $TARGET --features serde --verbose
|
||||||
# Running iOS apps on OSX requires the simulator so we skip that for now
|
# Running iOS apps on macOS requires the Simulator so we skip that for now
|
||||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi
|
- if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --verbose; fi
|
||||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi
|
- if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --features serde --verbose; fi
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- |
|
- |
|
||||||
|
|
54
CHANGELOG.md
54
CHANGELOG.md
|
@ -1,5 +1,59 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- On macOS, implement `run_return`.
|
||||||
|
- On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`.
|
||||||
|
- On X11, performance is improved when rapidly calling `Window::set_cursor_icon`.
|
||||||
|
- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized.
|
||||||
|
- On Windows, unset `maximized` when manually changing the window's position or size.
|
||||||
|
- On Windows, add touch pressure information for touch events.
|
||||||
|
- On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`.
|
||||||
|
- On Wayland, fix event processing sometimes stalling when using OpenGL with vsync.
|
||||||
|
- Officially remove the Emscripten backend.
|
||||||
|
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
|
||||||
|
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
|
||||||
|
- On X11, allow setting mulitple `XWindowType`s.
|
||||||
|
- On iOS, fix null window on initial `HiDpiFactorChanged` event.
|
||||||
|
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
|
||||||
|
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized
|
||||||
|
by the user.
|
||||||
|
- On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events.
|
||||||
|
- On X11, return dummy monitor data to avoid panicking when no monitors exist.
|
||||||
|
- On X11, prevent stealing input focus when creating a new window.
|
||||||
|
Only steal input focus when entering fullscreen mode.
|
||||||
|
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced
|
||||||
|
|
||||||
|
# 0.20.0 Alpha 3 (2019-08-14)
|
||||||
|
|
||||||
|
- On macOS, drop the run closure on exit.
|
||||||
|
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
|
||||||
|
- On X11, fix delayed events after window redraw.
|
||||||
|
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
|
||||||
|
- On Windows, screen saver won't start if the window is in fullscreen mode.
|
||||||
|
- Change all occurrences of the `new_user_event` method to `with_user_event`.
|
||||||
|
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
|
||||||
|
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
|
||||||
|
consists of `Fullscreen::Exclusive(VideoMode)` and
|
||||||
|
`Fullscreen::Borderless(MonitorHandle)` variants.
|
||||||
|
- Adds support for exclusive fullscreen mode.
|
||||||
|
- On iOS, add support for hiding the home indicator.
|
||||||
|
- On iOS, add support for deferring system gestures.
|
||||||
|
- On iOS, fix a crash that occurred while acquiring a monitor's name.
|
||||||
|
- On iOS, fix armv7-apple-ios compile target.
|
||||||
|
- Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy<T>`.
|
||||||
|
- On iOS, disable overscan compensation for external displays (removes black
|
||||||
|
bars surrounding the image).
|
||||||
|
- On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait.
|
||||||
|
- On iOS, add `set_prefers_status_bar_hidden` extension function instead of
|
||||||
|
hijacking `set_decorations` for this purpose.
|
||||||
|
- On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`.
|
||||||
|
- On iOS, add touch pressure information for touch events.
|
||||||
|
- Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms.
|
||||||
|
- On macOS, fix the signature of `-[NSView drawRect:]`.
|
||||||
|
- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application.
|
||||||
|
- On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number.
|
||||||
|
- On iOS, RedrawRequested now works for gl/metal backed views.
|
||||||
|
- On iOS, RedrawRequested is generally ordered after EventsCleared.
|
||||||
|
|
||||||
# 0.20.0 Alpha 2 (2019-07-09)
|
# 0.20.0 Alpha 2 (2019-07-09)
|
||||||
|
|
||||||
- On X11, non-resizable windows now have maximize explicitly disabled.
|
- On X11, non-resizable windows now have maximize explicitly disabled.
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "winit"
|
name = "winit"
|
||||||
version = "0.20.0-alpha2"
|
version = "0.20.0-alpha3"
|
||||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||||
description = "Cross-platform window creation library."
|
description = "Cross-platform window creation library."
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
@ -25,6 +25,7 @@ libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||||
derivative = "1.0.2"
|
derivative = "1.0.2"
|
||||||
|
raw-window-handle = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
image = "0.21"
|
image = "0.21"
|
||||||
|
@ -48,7 +49,7 @@ version = "0.1.3"
|
||||||
default_features = false
|
default_features = false
|
||||||
features = ["display_link"]
|
features = ["display_link"]
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||||
|
@ -80,10 +81,10 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "
|
||||||
calloop = "0.4.2"
|
calloop = "0.4.2"
|
||||||
smithay-client-toolkit = "0.6"
|
smithay-client-toolkit = "0.6"
|
||||||
x11-dl = "2.18.3"
|
x11-dl = "2.18.3"
|
||||||
percent-encoding = "1.0"
|
percent-encoding = "2.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
||||||
version = "0.8"
|
version = "0.9"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
||||||
package = "web-sys"
|
package = "web-sys"
|
||||||
|
@ -116,3 +117,4 @@ optional = true
|
||||||
package = "stdweb"
|
package = "stdweb"
|
||||||
version = "0.4.18"
|
version = "0.4.18"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
|
86
FEATURES.md
86
FEATURES.md
|
@ -12,7 +12,6 @@ be used to create both games and applications. It supports the main graphical pl
|
||||||
- iOS
|
- iOS
|
||||||
- Android
|
- Android
|
||||||
- Web
|
- Web
|
||||||
- via Emscripten
|
|
||||||
- via WASM
|
- via WASM
|
||||||
|
|
||||||
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
||||||
|
@ -84,6 +83,9 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||||
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
|
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
|
||||||
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
||||||
creation.
|
creation.
|
||||||
|
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
||||||
|
for fullscreen windows, and if applicable, captures the monitor for exclusive
|
||||||
|
use by this application.
|
||||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
||||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
||||||
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
||||||
|
@ -100,6 +102,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||||
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
|
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
|
||||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
||||||
- **Touch events**: Single-touch events.
|
- **Touch events**: Single-touch events.
|
||||||
|
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
||||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
||||||
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
||||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||||
|
@ -129,6 +132,21 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||||
* GTK Theme Variant
|
* GTK Theme Variant
|
||||||
* Base window size
|
* Base window size
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
* `winit` has a minimum OS requirement of iOS 8
|
||||||
|
* Get the `UIWindow` object pointer
|
||||||
|
* Get the `UIViewController` object pointer
|
||||||
|
* Get the `UIView` object pointer
|
||||||
|
* Get the `UIScreen` object pointer
|
||||||
|
* Setting the `UIView` hidpi factor
|
||||||
|
* Valid orientations
|
||||||
|
* Home indicator visibility
|
||||||
|
* Status bar visibility
|
||||||
|
* Deferrring system gestures
|
||||||
|
* Support for custom `UIView` derived class
|
||||||
|
* Getting the device idiom
|
||||||
|
* Getting the preferred video mode
|
||||||
|
|
||||||
## Usability
|
## Usability
|
||||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||||
|
|
||||||
|
@ -143,55 +161,57 @@ Legend:
|
||||||
- ❓: Unknown status
|
- ❓: Unknown status
|
||||||
|
|
||||||
### Windowing
|
### Windowing
|
||||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten|
|
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |
|
||||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ |
|
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ |
|
||||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |
|
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |
|
||||||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** |
|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
||||||
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** |
|
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|❓ |
|
||||||
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ |
|
||||||
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ |
|
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ |
|
||||||
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ |
|
||||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ |
|
||||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ |
|
||||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ |
|
||||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ |
|
||||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❓ |
|
||||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ |
|
||||||
|
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||||
|
|
||||||
### System information
|
### System information
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|
||||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ |
|
||||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ |
|
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❓ |
|
||||||
|
|
||||||
### Input handling
|
### Input handling
|
||||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|❓ |
|
||||||
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A** |
|
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|❓ |
|
||||||
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ |
|
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|❓ |
|
||||||
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ |
|
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❓ |
|
||||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❓ |
|
||||||
|Multitouch |❓ |❌ |✔️ |✔️ |❓ |❌ |❌ |
|
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❓ |
|
||||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❓ |
|
||||||
|
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |❓ |
|
||||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
||||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ |
|
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|
||||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❌ |
|
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❌ |
|
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|
||||||
|
|
||||||
### Pending API Reworks
|
### Pending API Reworks
|
||||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
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 |Emscripten|
|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ |
|
||||||
|Event Loop 2.0 ([#459]) |✔️ |❌ |❌ |✔️ |❌ |❌ |❌ |
|
|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ |
|
||||||
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||||
|
|
||||||
### Completed API Reworks
|
### Completed API Reworks
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
||||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||||
|
|
||||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
[#165]: https://github.com/rust-windowing/winit/issues/165
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = "0.20.0-alpha2"
|
winit = "0.20.0-alpha3"
|
||||||
```
|
```
|
||||||
|
|
||||||
## [Documentation](https://docs.rs/winit)
|
## [Documentation](https://docs.rs/winit)
|
||||||
|
@ -66,7 +66,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||||
|
|
||||||
### Platform-specific usage
|
### Platform-specific usage
|
||||||
|
|
||||||
#### Emscripten and WebAssembly
|
#### WebAssembly
|
||||||
|
|
||||||
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
|
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
|
||||||
|
|
||||||
|
|
41
examples/custom_events.rs
Normal file
41
examples/custom_events.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use winit::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::WindowBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum CustomEvent {
|
||||||
|
Timer,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let event_loop = EventLoop::<CustomEvent>::with_user_event();
|
||||||
|
|
||||||
|
let _window = WindowBuilder::new()
|
||||||
|
.with_title("A fantastic window!")
|
||||||
|
.build(&event_loop)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
|
||||||
|
// loop from any thread.
|
||||||
|
let event_loop_proxy = event_loop.create_proxy();
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
// Wake up the `event_loop` once every second and dispatch a custom event
|
||||||
|
// from a different thread.
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
|
event_loop_proxy.send_event(CustomEvent::Timer).ok();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| match event {
|
||||||
|
Event::UserEvent(event) => println!("user event: {:?}", event),
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => *control_flow = ControlFlow::Exit,
|
||||||
|
_ => *control_flow = ControlFlow::Wait,
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,56 +1,35 @@
|
||||||
use std::io::{self, Write};
|
use std::io::{stdin, stdout, Write};
|
||||||
use winit::{
|
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
event_loop::{ControlFlow, EventLoop},
|
use winit::monitor::{MonitorHandle, VideoMode};
|
||||||
monitor::MonitorHandle,
|
use winit::window::{Fullscreen, WindowBuilder};
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||||
let mut macos_use_simple_fullscreen = false;
|
stdout().flush().unwrap();
|
||||||
|
|
||||||
let monitor = {
|
let mut num = String::new();
|
||||||
// On macOS there are two fullscreen modes "native" and "simple"
|
stdin().read_line(&mut num).unwrap();
|
||||||
#[cfg(target_os = "macos")]
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
{
|
|
||||||
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
|
|
||||||
io::stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let mut num = String::new();
|
let fullscreen = Some(match num {
|
||||||
io::stdin().read_line(&mut num).unwrap();
|
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
|
||||||
match num {
|
_ => panic!("Please enter a valid number"),
|
||||||
2 => macos_use_simple_fullscreen = true,
|
});
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt for monitor when using native fullscreen
|
|
||||||
if !macos_use_simple_fullscreen {
|
|
||||||
Some(prompt_for_monitor(&event_loop))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
Some(prompt_for_monitor(&event_loop))
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut is_fullscreen = monitor.is_some();
|
|
||||||
let mut is_maximized = false;
|
let mut is_maximized = false;
|
||||||
let mut decorations = true;
|
let mut decorations = true;
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = WindowBuilder::new()
|
||||||
.with_title("Hello world!")
|
.with_title("Hello world!")
|
||||||
.with_fullscreen(monitor)
|
.with_fullscreen(fullscreen.clone())
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
println!("{:?}", event);
|
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
@ -67,35 +46,14 @@ fn main() {
|
||||||
} => match (virtual_code, state) {
|
} => match (virtual_code, state) {
|
||||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||||
#[cfg(target_os = "macos")]
|
if window.fullscreen().is_some() {
|
||||||
{
|
|
||||||
if macos_use_simple_fullscreen {
|
|
||||||
use winit::platform::macos::WindowExtMacOS;
|
|
||||||
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
|
|
||||||
is_fullscreen = !is_fullscreen;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is_fullscreen = !is_fullscreen;
|
|
||||||
if !is_fullscreen {
|
|
||||||
window.set_fullscreen(None);
|
window.set_fullscreen(None);
|
||||||
} else {
|
} else {
|
||||||
window.set_fullscreen(Some(window.current_monitor()));
|
window.set_fullscreen(fullscreen.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||||
println!("window.fullscreen {:?}", window.fullscreen());
|
println!("window.fullscreen {:?}", window.fullscreen());
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use winit::platform::macos::WindowExtMacOS;
|
|
||||||
println!(
|
|
||||||
"window.simple_fullscreen {:?}",
|
|
||||||
WindowExtMacOS::simple_fullscreen(&window)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||||
is_maximized = !is_maximized;
|
is_maximized = !is_maximized;
|
||||||
|
@ -121,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
print!("Please write the number of the monitor to use: ");
|
print!("Please write the number of the monitor to use: ");
|
||||||
io::stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
|
|
||||||
let mut num = String::new();
|
let mut num = String::new();
|
||||||
io::stdin().read_line(&mut num).unwrap();
|
stdin().read_line(&mut num).unwrap();
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
let monitor = event_loop
|
let monitor = event_loop
|
||||||
.available_monitors()
|
.available_monitors()
|
||||||
|
@ -135,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||||
|
|
||||||
monitor
|
monitor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
|
||||||
|
for (i, video_mode) in monitor.video_modes().enumerate() {
|
||||||
|
println!("Video mode #{}: {}", i, video_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("Please write the number of the video mode to use: ");
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
|
||||||
|
let mut num = String::new();
|
||||||
|
stdin().read_line(&mut num).unwrap();
|
||||||
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
|
let video_mode = monitor
|
||||||
|
.video_modes()
|
||||||
|
.nth(num)
|
||||||
|
.expect("Please enter a valid ID");
|
||||||
|
|
||||||
|
println!("Using {}", video_mode);
|
||||||
|
|
||||||
|
video_mode
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,15 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||||
|
=======
|
||||||
|
use winit::{
|
||||||
|
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::{CursorIcon, Fullscreen, WindowBuilder},
|
||||||
|
};
|
||||||
|
>>>>>>> master
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||||
|
@ -21,11 +29,34 @@ fn main() {
|
||||||
.with_inner_size(WINDOW_SIZE.into())
|
.with_inner_size(WINDOW_SIZE.into())
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
|
||||||
|
let mut video_mode_id = 0usize;
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
window_senders.insert(window.id(), tx);
|
window_senders.insert(window.id(), tx);
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
while let Ok(event) = rx.recv() {
|
while let Ok(event) = rx.recv() {
|
||||||
match event {
|
match event {
|
||||||
|
WindowEvent::Moved { .. } => {
|
||||||
|
// We need to update our chosen video mode if the window
|
||||||
|
// was moved to an another monitor, so that the window
|
||||||
|
// appears on this monitor instead when we go fullscreen
|
||||||
|
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
|
||||||
|
video_modes = window.current_monitor().video_modes().collect();
|
||||||
|
video_mode_id = video_mode_id.min(video_modes.len());
|
||||||
|
let video_mode = video_modes.iter().nth(video_mode_id);
|
||||||
|
|
||||||
|
// Different monitors may support different video modes,
|
||||||
|
// and the index we chose previously may now point to a
|
||||||
|
// completely different video mode, so notify the user
|
||||||
|
if video_mode != previous_video_mode.as_ref() {
|
||||||
|
println!(
|
||||||
|
"Window moved to another monitor, picked video mode: {}",
|
||||||
|
video_modes.iter().nth(video_mode_id).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput {
|
||||||
input:
|
input:
|
||||||
KeyboardInput {
|
KeyboardInput {
|
||||||
|
@ -46,9 +77,26 @@ fn main() {
|
||||||
false => CursorIcon::Default,
|
false => CursorIcon::Default,
|
||||||
}),
|
}),
|
||||||
D => window.set_decorations(!state),
|
D => window.set_decorations(!state),
|
||||||
F => window.set_fullscreen(match state {
|
// Cycle through video modes
|
||||||
true => Some(window.current_monitor()),
|
Right | Left => {
|
||||||
false => None,
|
video_mode_id = match key {
|
||||||
|
Left => video_mode_id.saturating_sub(1),
|
||||||
|
Right => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"Picking video mode: {}",
|
||||||
|
video_modes.iter().nth(video_mode_id).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
F => window.set_fullscreen(match (state, modifiers.alt) {
|
||||||
|
(true, false) => {
|
||||||
|
Some(Fullscreen::Borderless(window.current_monitor()))
|
||||||
|
}
|
||||||
|
(true, true) => Some(Fullscreen::Exclusive(
|
||||||
|
video_modes.iter().nth(video_mode_id).unwrap().clone(),
|
||||||
|
)),
|
||||||
|
(false, _) => None,
|
||||||
}),
|
}),
|
||||||
G => window.set_cursor_grab(state).unwrap(),
|
G => window.set_cursor_grab(state).unwrap(),
|
||||||
H => window.set_cursor_visible(!state),
|
H => window.set_cursor_visible(!state),
|
||||||
|
@ -58,6 +106,7 @@ fn main() {
|
||||||
println!("-> inner_position : {:?}", window.inner_position());
|
println!("-> inner_position : {:?}", window.inner_position());
|
||||||
println!("-> outer_size : {:?}", window.outer_size());
|
println!("-> outer_size : {:?}", window.outer_size());
|
||||||
println!("-> inner_size : {:?}", window.inner_size());
|
println!("-> inner_size : {:?}", window.inner_size());
|
||||||
|
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||||
}
|
}
|
||||||
L => window.set_min_inner_size(match state {
|
L => window.set_min_inner_size(match state {
|
||||||
true => Some(WINDOW_SIZE.into()),
|
true => Some(WINDOW_SIZE.into()),
|
||||||
|
@ -110,6 +159,7 @@ fn main() {
|
||||||
| WindowEvent::KeyboardInput {
|
| WindowEvent::KeyboardInput {
|
||||||
input:
|
input:
|
||||||
KeyboardInput {
|
KeyboardInput {
|
||||||
|
state: ElementState::Released,
|
||||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
fn main() {
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
let event_loop: EventLoop<i32> = EventLoop::new_user_event();
|
|
||||||
|
|
||||||
let _window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let proxy = event_loop.create_proxy();
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let mut counter = 0;
|
|
||||||
// Wake up the `event_loop` once every second.
|
|
||||||
loop {
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
||||||
proxy.send_event(counter).unwrap();
|
|
||||||
counter += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
println!("{:?}", event);
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
_ => *control_flow = ControlFlow::Wait,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
fn main() {
|
|
||||||
panic!("Example not supported on Wasm");
|
|
||||||
}
|
|
|
@ -7,6 +7,6 @@ fn main() {
|
||||||
println!("Listing available video modes:");
|
println!("Listing available video modes:");
|
||||||
|
|
||||||
for mode in monitor.video_modes() {
|
for mode in monitor.video_modes() {
|
||||||
println!("{:?}", mode);
|
println!("{}", mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,17 @@
|
||||||
|
<<<<<<< HEAD
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
=======
|
||||||
|
// Limit this example to only compatible platforms.
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "windows",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
))]
|
||||||
|
>>>>>>> master
|
||||||
fn main() {
|
fn main() {
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
|
@ -6,6 +19,10 @@ fn main() {
|
||||||
platform::desktop::EventLoopExtDesktop,
|
platform::desktop::EventLoopExtDesktop,
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
|
||||||
|
>>>>>>> master
|
||||||
let mut event_loop = EventLoop::new();
|
let mut event_loop = EventLoop::new();
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = WindowBuilder::new()
|
||||||
|
@ -40,7 +57,13 @@ fn main() {
|
||||||
println!("Okay we're done now for real.");
|
println!("Okay we're done now for real.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
fn main() {
|
fn main() {
|
||||||
panic!("Example not supported on Wasm");
|
panic!("Example not supported on Wasm");
|
||||||
|
=======
|
||||||
|
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||||
|
fn main() {
|
||||||
|
println!("This platform doesn't support run_return.");
|
||||||
|
>>>>>>> master
|
||||||
}
|
}
|
||||||
|
|
92
src/event.rs
92
src/event.rs
|
@ -134,6 +134,10 @@ pub enum WindowEvent {
|
||||||
input: KeyboardInput,
|
input: KeyboardInput,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Keyboard modifiers have changed
|
||||||
|
#[doc(hidden)]
|
||||||
|
ModifiersChanged { modifiers: ModifiersState },
|
||||||
|
|
||||||
/// The cursor has moved on the window.
|
/// The cursor has moved on the window.
|
||||||
CursorMoved {
|
CursorMoved {
|
||||||
device_id: DeviceId,
|
device_id: DeviceId,
|
||||||
|
@ -304,30 +308,94 @@ pub enum TouchPhase {
|
||||||
Cancelled,
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents touch event
|
/// Represents a touch event
|
||||||
///
|
///
|
||||||
/// Every time user touches screen new Start event with some finger id is generated.
|
/// Every time the user touches the screen, a new `Start` event with an unique
|
||||||
/// When the finger is removed from the screen End event with same id is generated.
|
/// identifier for the finger is generated. When the finger is lifted, an `End`
|
||||||
|
/// event is generated with the same finger id.
|
||||||
///
|
///
|
||||||
/// For every id there will be at least 2 events with phases Start and End (or Cancelled).
|
/// After a `Start` event has been emitted, there may be zero or more `Move`
|
||||||
/// There may be 0 or more Move events.
|
/// events when the finger is moved or the touch pressure changes.
|
||||||
///
|
///
|
||||||
|
/// The finger id may be reused by the system after an `End` event. The user
|
||||||
|
/// should assume that a new `Start` event received with the same id has nothing
|
||||||
|
/// to do with the old finger and is a new finger.
|
||||||
///
|
///
|
||||||
/// Depending on platform implementation id may or may not be reused by system after End event.
|
/// A `Cancelled` event is emitted when the system has canceled tracking this
|
||||||
///
|
/// touch, such as when the window loses focus, or on iOS if the user moves the
|
||||||
/// Gesture regonizer using this event should assume that Start event received with same id
|
/// device against their face.
|
||||||
/// as previously received End event is a new finger and has nothing to do with an old one.
|
|
||||||
///
|
|
||||||
/// Touch may be cancelled if for example window lost focus.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Touch {
|
pub struct Touch {
|
||||||
pub device_id: DeviceId,
|
pub device_id: DeviceId,
|
||||||
pub phase: TouchPhase,
|
pub phase: TouchPhase,
|
||||||
pub location: LogicalPosition,
|
pub location: LogicalPosition,
|
||||||
/// unique identifier of a finger.
|
/// Describes how hard the screen was pressed. May be `None` if the platform
|
||||||
|
/// does not support pressure sensitivity.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
|
||||||
|
pub force: Option<Force>,
|
||||||
|
/// Unique identifier of a finger.
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes the force of a touch event
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Force {
|
||||||
|
/// On iOS, the force is calibrated so that the same number corresponds to
|
||||||
|
/// roughly the same amount of pressure on the screen regardless of the
|
||||||
|
/// device.
|
||||||
|
Calibrated {
|
||||||
|
/// The force of the touch, where a value of 1.0 represents the force of
|
||||||
|
/// an average touch (predetermined by the system, not user-specific).
|
||||||
|
///
|
||||||
|
/// The force reported by Apple Pencil is measured along the axis of the
|
||||||
|
/// pencil. If you want a force perpendicular to the device, you need to
|
||||||
|
/// calculate this value using the `altitude_angle` value.
|
||||||
|
force: f64,
|
||||||
|
/// The maximum possible force for a touch.
|
||||||
|
///
|
||||||
|
/// The value of this field is sufficiently high to provide a wide
|
||||||
|
/// dynamic range for values of the `force` field.
|
||||||
|
max_possible_force: f64,
|
||||||
|
/// The altitude (in radians) of the stylus.
|
||||||
|
///
|
||||||
|
/// A value of 0 radians indicates that the stylus is parallel to the
|
||||||
|
/// surface. The value of this property is Pi/2 when the stylus is
|
||||||
|
/// perpendicular to the surface.
|
||||||
|
altitude_angle: Option<f64>,
|
||||||
|
},
|
||||||
|
/// If the platform reports the force as normalized, we have no way of
|
||||||
|
/// knowing how much pressure 1.0 corresponds to – we know it's the maximum
|
||||||
|
/// amount of force, but as to how much force, you might either have to
|
||||||
|
/// press really really hard, or not hard at all, depending on the device.
|
||||||
|
Normalized(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Force {
|
||||||
|
/// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
|
||||||
|
/// Instead of normalizing the force, you should prefer to handle
|
||||||
|
/// `Force::Calibrated` so that the amount of force the user has to apply is
|
||||||
|
/// consistent across devices.
|
||||||
|
pub fn normalized(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
Force::Calibrated {
|
||||||
|
force,
|
||||||
|
max_possible_force,
|
||||||
|
altitude_angle,
|
||||||
|
} => {
|
||||||
|
let force = match altitude_angle {
|
||||||
|
Some(altitude_angle) => force / altitude_angle.sin(),
|
||||||
|
None => *force,
|
||||||
|
};
|
||||||
|
force / max_possible_force
|
||||||
|
}
|
||||||
|
Force::Normalized(force) => *force,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Hardware-dependent keyboard scan code.
|
/// Hardware-dependent keyboard scan code.
|
||||||
pub type ScanCode = u32;
|
pub type ScanCode = u32;
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,10 @@ pub struct EventLoop<T: 'static> {
|
||||||
|
|
||||||
/// Target that associates windows with an `EventLoop`.
|
/// Target that associates windows with an `EventLoop`.
|
||||||
///
|
///
|
||||||
/// This type exists to allow you to create new windows while Winit executes your callback.
|
/// This type exists to allow you to create new windows while Winit executes
|
||||||
/// `EventLoop` will coerce into this type, so functions that take this as a parameter can also
|
/// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
|
||||||
/// take `&EventLoop`.
|
/// EventLoop<T>`), so functions that take this as a parameter can also take
|
||||||
|
/// `&EventLoop`.
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
pub struct EventLoopWindowTarget<T: 'static> {
|
||||||
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
||||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||||
|
@ -99,7 +100,7 @@ impl EventLoop<()> {
|
||||||
///
|
///
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
/// - **iOS:** Can only be called on the main thread.
|
||||||
pub fn new() -> EventLoop<()> {
|
pub fn new() -> EventLoop<()> {
|
||||||
EventLoop::<()>::new_user_event()
|
EventLoop::<()>::with_user_event()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +115,7 @@ impl<T> EventLoop<T> {
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
/// - **iOS:** Can only be called on the main thread.
|
||||||
pub fn new_user_event() -> EventLoop<T> {
|
pub fn with_user_event() -> EventLoop<T> {
|
||||||
EventLoop {
|
EventLoop {
|
||||||
event_loop: platform_impl::EventLoop::new(),
|
event_loop: platform_impl::EventLoop::new(),
|
||||||
_marker: ::std::marker::PhantomData,
|
_marker: ::std::marker::PhantomData,
|
||||||
|
@ -172,11 +173,18 @@ impl<T> Deref for EventLoop<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to send custom events to `EventLoop`.
|
/// Used to send custom events to `EventLoop`.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
pub struct EventLoopProxy<T: 'static> {
|
||||||
event_loop_proxy: platform_impl::EventLoopProxy<T>,
|
event_loop_proxy: platform_impl::EventLoopProxy<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
event_loop_proxy: self.event_loop_proxy.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
impl<T: 'static> EventLoopProxy<T> {
|
||||||
/// Send an event to the `EventLoop` from which this proxy was created. This emits a
|
/// Send an event to the `EventLoop` from which this proxy was created. This emits a
|
||||||
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
|
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
|
||||||
|
|
|
@ -121,10 +121,9 @@ extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
extern crate derivative;
|
extern crate derivative;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "ios", target_os = "windows"))]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -52,17 +52,41 @@ impl Iterator for AvailableMonitorsIter {
|
||||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||||
///
|
///
|
||||||
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
|
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Derivative)]
|
||||||
|
#[derivative(Clone, Debug = "transparent", PartialEq, Eq, Hash)]
|
||||||
pub struct VideoMode {
|
pub struct VideoMode {
|
||||||
pub(crate) size: (u32, u32),
|
pub(crate) video_mode: platform_impl::VideoMode,
|
||||||
pub(crate) bit_depth: u16,
|
}
|
||||||
pub(crate) refresh_rate: u16,
|
|
||||||
|
impl PartialOrd for VideoMode {
|
||||||
|
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
size.cmp(&other_size)
|
||||||
|
.then(
|
||||||
|
self.refresh_rate()
|
||||||
|
.cmp(&other.refresh_rate())
|
||||||
|
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||||
|
)
|
||||||
|
.reverse(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoMode {
|
impl VideoMode {
|
||||||
/// Returns the resolution of this video mode.
|
/// Returns the resolution of this video mode.
|
||||||
|
#[inline]
|
||||||
pub fn size(&self) -> PhysicalSize {
|
pub fn size(&self) -> PhysicalSize {
|
||||||
self.size.into()
|
self.video_mode.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the bit depth of this video mode, as in how many bits you have
|
/// Returns the bit depth of this video mode, as in how many bits you have
|
||||||
|
@ -73,15 +97,37 @@ impl VideoMode {
|
||||||
///
|
///
|
||||||
/// - **Wayland:** Always returns 32.
|
/// - **Wayland:** Always returns 32.
|
||||||
/// - **iOS:** Always returns 32.
|
/// - **iOS:** Always returns 32.
|
||||||
|
#[inline]
|
||||||
pub fn bit_depth(&self) -> u16 {
|
pub fn bit_depth(&self) -> u16 {
|
||||||
self.bit_depth
|
self.video_mode.bit_depth()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the refresh rate of this video mode. **Note**: the returned
|
/// Returns the refresh rate of this video mode. **Note**: the returned
|
||||||
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
||||||
/// value to be exact.
|
/// value to be exact.
|
||||||
|
#[inline]
|
||||||
pub fn refresh_rate(&self) -> u16 {
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
self.refresh_rate
|
self.video_mode.refresh_rate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the monitor that this video mode is valid for. Each monitor has
|
||||||
|
/// a separate set of valid video modes.
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> MonitorHandle {
|
||||||
|
self.video_mode.monitor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VideoMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}x{} @ {} Hz ({} bpp)",
|
||||||
|
self.size().width,
|
||||||
|
self.size().height,
|
||||||
|
self.refresh_rate(),
|
||||||
|
self.bit_depth()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +136,7 @@ impl VideoMode {
|
||||||
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
||||||
///
|
///
|
||||||
/// [`Window`]: ../window/struct.Window.html
|
/// [`Window`]: ../window/struct.Window.html
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
pub(crate) inner: platform_impl::MonitorHandle,
|
pub(crate) inner: platform_impl::MonitorHandle,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,13 @@ use std::os::raw::c_void;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
event_loop::EventLoop,
|
event_loop::EventLoop,
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::{Window, WindowBuilder},
|
window::{Window, WindowBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to iOS.
|
/// Additional methods on [`EventLoop`] that are specific to iOS.
|
||||||
pub trait EventLoopExtIOS {
|
pub trait EventLoopExtIOS {
|
||||||
/// Returns the idiom (phone/tablet/tv/etc) for the current device.
|
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
|
||||||
fn idiom(&self) -> Idiom;
|
fn idiom(&self) -> Idiom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,32 +20,80 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods on `Window` that are specific to iOS.
|
/// Additional methods on [`Window`] that are specific to iOS.
|
||||||
pub trait WindowExtIOS {
|
pub trait WindowExtIOS {
|
||||||
/// Returns a pointer to the `UIWindow` that is used by this window.
|
/// Returns a pointer to the [`UIWindow`] that is used by this window.
|
||||||
///
|
///
|
||||||
/// The pointer will become invalid when the `Window` is destroyed.
|
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||||
|
///
|
||||||
|
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||||
fn ui_window(&self) -> *mut c_void;
|
fn ui_window(&self) -> *mut c_void;
|
||||||
|
|
||||||
/// Returns a pointer to the `UIViewController` that is used by this window.
|
/// Returns a pointer to the [`UIViewController`] that is used by this window.
|
||||||
///
|
///
|
||||||
/// The pointer will become invalid when the `Window` is destroyed.
|
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||||
|
///
|
||||||
|
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
|
||||||
fn ui_view_controller(&self) -> *mut c_void;
|
fn ui_view_controller(&self) -> *mut c_void;
|
||||||
|
|
||||||
/// Returns a pointer to the `UIView` that is used by this window.
|
/// Returns a pointer to the [`UIView`] that is used by this window.
|
||||||
///
|
///
|
||||||
/// The pointer will become invalid when the `Window` is destroyed.
|
/// The pointer will become invalid when the [`Window`] is destroyed.
|
||||||
|
///
|
||||||
|
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
|
||||||
fn ui_view(&self) -> *mut c_void;
|
fn ui_view(&self) -> *mut c_void;
|
||||||
|
|
||||||
/// Sets the HiDpi factor used by this window.
|
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
|
||||||
///
|
///
|
||||||
/// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`.
|
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
||||||
|
/// this to [`MonitorHandle::hidpi_factor()`].
|
||||||
|
///
|
||||||
|
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||||
|
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
||||||
fn set_hidpi_factor(&self, hidpi_factor: f64);
|
fn set_hidpi_factor(&self, hidpi_factor: f64);
|
||||||
|
|
||||||
/// Sets the valid orientations for screens showing this `Window`.
|
/// Sets the valid orientations for the [`Window`].
|
||||||
///
|
///
|
||||||
/// On iPhones and iPods upside down portrait is never enabled.
|
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
||||||
|
///
|
||||||
|
/// This changes the value returned by
|
||||||
|
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
|
||||||
|
/// and then calls
|
||||||
|
/// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
|
||||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
|
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
|
||||||
|
|
||||||
|
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
||||||
|
///
|
||||||
|
/// The default is to prefer showing the home indicator.
|
||||||
|
///
|
||||||
|
/// This changes the value returned by
|
||||||
|
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
|
||||||
|
/// and then calls
|
||||||
|
/// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
|
||||||
|
///
|
||||||
|
/// This only has an effect on iOS 11.0+.
|
||||||
|
fn set_prefers_home_indicator_hidden(&self, hidden: bool);
|
||||||
|
|
||||||
|
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
||||||
|
/// application's touch handling.
|
||||||
|
///
|
||||||
|
/// This changes the value returned by
|
||||||
|
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
|
||||||
|
/// and then calls
|
||||||
|
/// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
|
||||||
|
///
|
||||||
|
/// This only has an effect on iOS 11.0+.
|
||||||
|
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
|
||||||
|
|
||||||
|
/// Sets whether the [`Window`] prefers the status bar hidden.
|
||||||
|
///
|
||||||
|
/// The default is to prefer showing the status bar.
|
||||||
|
///
|
||||||
|
/// This changes the value returned by
|
||||||
|
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
|
||||||
|
/// and then calls
|
||||||
|
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
|
||||||
|
fn set_prefers_status_bar_hidden(&self, hidden: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowExtIOS for Window {
|
impl WindowExtIOS for Window {
|
||||||
|
@ -73,23 +121,79 @@ impl WindowExtIOS for Window {
|
||||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||||
self.window.set_valid_orientations(valid_orientations)
|
self.window.set_valid_orientations(valid_orientations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||||
|
self.window.set_prefers_home_indicator_hidden(hidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||||
|
self.window
|
||||||
|
.set_preferred_screen_edges_deferring_system_gestures(edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||||
|
self.window.set_prefers_status_bar_hidden(hidden)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods on `WindowBuilder` that are specific to iOS.
|
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
|
||||||
pub trait WindowBuilderExtIOS {
|
pub trait WindowBuilderExtIOS {
|
||||||
/// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided.
|
/// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
|
||||||
///
|
///
|
||||||
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
|
/// 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;
|
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
|
||||||
|
|
||||||
/// Sets the `contentScaleFactor` of the underlying `UIWindow` to `hidpi_factor`.
|
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
|
||||||
///
|
///
|
||||||
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
||||||
/// this to `MonitorHandle::hidpi_factor()`.
|
/// this to [`MonitorHandle::hidpi_factor()`].
|
||||||
|
///
|
||||||
|
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
||||||
|
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
||||||
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
|
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
|
||||||
|
|
||||||
/// Sets the valid orientations for the `Window`.
|
/// Sets the valid orientations for the [`Window`].
|
||||||
|
///
|
||||||
|
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
||||||
|
///
|
||||||
|
/// This sets the initial value returned by
|
||||||
|
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
|
||||||
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
|
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
|
||||||
|
|
||||||
|
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
||||||
|
///
|
||||||
|
/// The default is to prefer showing the home indicator.
|
||||||
|
///
|
||||||
|
/// This sets the initial value returned by
|
||||||
|
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
|
||||||
|
///
|
||||||
|
/// This only has an effect on iOS 11.0+.
|
||||||
|
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
|
||||||
|
|
||||||
|
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
||||||
|
/// application's touch handling.
|
||||||
|
///
|
||||||
|
/// This sets the initial value returned by
|
||||||
|
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
|
||||||
|
///
|
||||||
|
/// This only has an effect on iOS 11.0+.
|
||||||
|
fn with_preferred_screen_edges_deferring_system_gestures(
|
||||||
|
self,
|
||||||
|
edges: ScreenEdge,
|
||||||
|
) -> WindowBuilder;
|
||||||
|
|
||||||
|
/// Sets whether the [`Window`] prefers the status bar hidden.
|
||||||
|
///
|
||||||
|
/// The default is to prefer showing the status bar.
|
||||||
|
///
|
||||||
|
/// This sets the initial value returned by
|
||||||
|
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
|
||||||
|
fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowBuilderExtIOS for WindowBuilder {
|
impl WindowBuilderExtIOS for WindowBuilder {
|
||||||
|
@ -110,12 +214,41 @@ impl WindowBuilderExtIOS for WindowBuilder {
|
||||||
self.platform_specific.valid_orientations = valid_orientations;
|
self.platform_specific.valid_orientations = valid_orientations;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||||
|
self.platform_specific.prefers_home_indicator_hidden = hidden;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_preferred_screen_edges_deferring_system_gestures(
|
||||||
|
mut self,
|
||||||
|
edges: ScreenEdge,
|
||||||
|
) -> WindowBuilder {
|
||||||
|
self.platform_specific
|
||||||
|
.preferred_screen_edges_deferring_system_gestures = edges;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
|
||||||
|
self.platform_specific.prefers_status_bar_hidden = hidden;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods on `MonitorHandle` that are specific to iOS.
|
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
|
||||||
pub trait MonitorHandleExtIOS {
|
pub trait MonitorHandleExtIOS {
|
||||||
/// Returns a pointer to the `UIScreen` that is used by this monitor.
|
/// Returns a pointer to the [`UIScreen`] that is used by this monitor.
|
||||||
|
///
|
||||||
|
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
|
||||||
fn ui_screen(&self) -> *mut c_void;
|
fn ui_screen(&self) -> *mut c_void;
|
||||||
|
|
||||||
|
/// Returns the preferred [`VideoMode`] for this monitor.
|
||||||
|
///
|
||||||
|
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
|
||||||
|
fn preferred_video_mode(&self) -> VideoMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonitorHandleExtIOS for MonitorHandle {
|
impl MonitorHandleExtIOS for MonitorHandle {
|
||||||
|
@ -123,9 +256,14 @@ impl MonitorHandleExtIOS for MonitorHandle {
|
||||||
fn ui_screen(&self) -> *mut c_void {
|
fn ui_screen(&self) -> *mut c_void {
|
||||||
self.inner.ui_screen() as _
|
self.inner.ui_screen() as _
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn preferred_video_mode(&self) -> VideoMode {
|
||||||
|
self.inner.preferred_video_mode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Valid orientations for a particular `Window`.
|
/// Valid orientations for a particular [`Window`].
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum ValidOrientations {
|
pub enum ValidOrientations {
|
||||||
/// Excludes `PortraitUpsideDown` on iphone
|
/// Excludes `PortraitUpsideDown` on iphone
|
||||||
|
@ -161,3 +299,19 @@ pub enum Idiom {
|
||||||
TV,
|
TV,
|
||||||
CarPlay,
|
CarPlay,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// The [edges] of a screen.
|
||||||
|
///
|
||||||
|
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -129,6 +129,7 @@ pub trait WindowBuilderExtMacOS {
|
||||||
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
|
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
|
||||||
/// Build window with `resizeIncrements` property. Values must not be 0.
|
/// Build window with `resizeIncrements` property. Values must not be 0.
|
||||||
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
|
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
|
||||||
|
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||||
|
@ -182,6 +183,12 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||||
self.platform_specific.resize_increments = Some(increments.into());
|
self.platform_specific.resize_increments = Some(increments.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
|
||||||
|
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods on `MonitorHandle` that are specific to MacOS.
|
/// Additional methods on `MonitorHandle` that are specific to MacOS.
|
||||||
|
|
|
@ -6,14 +6,15 @@ use smithay_client_toolkit::window::{ButtonState, Theme};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
event_loop::EventLoop,
|
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||||
monitor::MonitorHandle,
|
monitor::MonitorHandle,
|
||||||
window::{Window, WindowBuilder},
|
window::{Window, WindowBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::platform_impl::{
|
use crate::platform_impl::{
|
||||||
x11::{ffi::XVisualInfo, XConnection},
|
x11::{ffi::XVisualInfo, XConnection},
|
||||||
EventLoop as LinuxEventLoop, Window as LinuxWindow,
|
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
||||||
|
Window as LinuxWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: stupid hack so that glutin can do its work
|
// TODO: stupid hack so that glutin can do its work
|
||||||
|
@ -90,6 +91,57 @@ impl Theme for WaylandThemeObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
|
||||||
|
pub trait EventLoopWindowTargetExtUnix {
|
||||||
|
/// True if the `EventLoopWindowTarget` uses Wayland.
|
||||||
|
fn is_wayland(&self) -> bool;
|
||||||
|
///
|
||||||
|
/// True if the `EventLoopWindowTarget` uses X11.
|
||||||
|
fn is_x11(&self) -> bool;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
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.
|
||||||
|
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
||||||
|
#[inline]
|
||||||
|
fn is_wayland(&self) -> bool {
|
||||||
|
self.p.is_wayland()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_x11(&self) -> bool {
|
||||||
|
!self.p.is_wayland()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
||||||
|
match self.p {
|
||||||
|
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||||
|
match self.p {
|
||||||
|
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
||||||
|
Some(p.display().get_display_ptr() as *mut _)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to Unix.
|
/// Additional methods on `EventLoop` that are specific to Unix.
|
||||||
pub trait EventLoopExtUnix {
|
pub trait EventLoopExtUnix {
|
||||||
/// Builds a new `EventLoops` that is forced to use X11.
|
/// Builds a new `EventLoops` that is forced to use X11.
|
||||||
|
@ -101,22 +153,6 @@ pub trait EventLoopExtUnix {
|
||||||
fn new_wayland() -> Self
|
fn new_wayland() -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
/// True if the `EventLoop` uses Wayland.
|
|
||||||
fn is_wayland(&self) -> bool;
|
|
||||||
|
|
||||||
/// True if the `EventLoop` uses X11.
|
|
||||||
fn is_x11(&self) -> bool;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
|
||||||
|
|
||||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventLoop`.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the glutin `EventLoop` is destroyed.
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> EventLoopExtUnix for EventLoop<T> {
|
impl<T> EventLoopExtUnix for EventLoop<T> {
|
||||||
|
@ -138,33 +174,6 @@ impl<T> EventLoopExtUnix for EventLoop<T> {
|
||||||
_marker: ::std::marker::PhantomData,
|
_marker: ::std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_wayland(&self) -> bool {
|
|
||||||
self.event_loop.is_wayland()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_x11(&self) -> bool {
|
|
||||||
!self.event_loop.is_wayland()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
|
||||||
match self.event_loop {
|
|
||||||
LinuxEventLoop::X(ref e) => Some(e.x_connection().clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.event_loop {
|
|
||||||
LinuxEventLoop::Wayland(ref e) => Some(e.display().get_display_ptr() as *mut _),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods on `Window` that are specific to Unix.
|
/// Additional methods on `Window` that are specific to Unix.
|
||||||
|
@ -258,17 +267,17 @@ impl WindowExtUnix for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
fn set_urgent(&self, is_urgent: bool) {
|
||||||
match self.window {
|
if let LinuxWindow::X(ref w) = self.window {
|
||||||
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
w.set_urgent(is_urgent);
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_urgent(&self, is_urgent: bool) {
|
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
||||||
if let LinuxWindow::X(ref w) = self.window {
|
match self.window {
|
||||||
w.set_urgent(is_urgent);
|
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,82 +313,82 @@ impl WindowExtUnix for Window {
|
||||||
|
|
||||||
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
||||||
pub trait WindowBuilderExtUnix {
|
pub trait WindowBuilderExtUnix {
|
||||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
|
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
|
||||||
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
|
fn with_x11_screen(self, screen_id: i32) -> Self;
|
||||||
|
|
||||||
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
|
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
|
||||||
fn with_class(self, class: String, instance: String) -> WindowBuilder;
|
fn with_class(self, class: String, instance: String) -> Self;
|
||||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
||||||
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
|
fn with_override_redirect(self, override_redirect: bool) -> Self;
|
||||||
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
|
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
|
||||||
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
|
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.
|
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
|
||||||
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder;
|
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
||||||
/// Build window with resize increment hint. Only implemented on X11.
|
/// Build window with resize increment hint. Only implemented on X11.
|
||||||
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
|
fn with_resize_increments(self, increments: LogicalSize) -> Self;
|
||||||
/// Build window with base size hint. Only implemented on X11.
|
/// Build window with base size hint. Only implemented on X11.
|
||||||
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder;
|
fn with_base_size(self, base_size: LogicalSize) -> Self;
|
||||||
|
|
||||||
/// Build window with a given application ID. It should match the `.desktop` file distributed with
|
/// Build window with a given application ID. It should match the `.desktop` file distributed with
|
||||||
/// your program. Only relevant on Wayland.
|
/// your program. Only relevant on Wayland.
|
||||||
///
|
///
|
||||||
/// For details about application ID conventions, see the
|
/// 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)
|
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
|
||||||
fn with_app_id(self, app_id: String) -> WindowBuilder;
|
fn with_app_id(self, app_id: String) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowBuilderExtUnix for WindowBuilder {
|
impl WindowBuilderExtUnix for WindowBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> WindowBuilder {
|
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
|
||||||
self.platform_specific.visual_infos =
|
self.platform_specific.visual_infos =
|
||||||
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
|
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_x11_screen(mut self, screen_id: i32) -> WindowBuilder {
|
fn with_x11_screen(mut self, screen_id: i32) -> Self {
|
||||||
self.platform_specific.screen_id = Some(screen_id);
|
self.platform_specific.screen_id = Some(screen_id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_class(mut self, instance: String, class: String) -> WindowBuilder {
|
fn with_class(mut self, instance: String, class: String) -> Self {
|
||||||
self.platform_specific.class = Some((instance, class));
|
self.platform_specific.class = Some((instance, class));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
|
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
|
||||||
self.platform_specific.override_redirect = override_redirect;
|
self.platform_specific.override_redirect = override_redirect;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder {
|
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
|
||||||
self.platform_specific.x11_window_type = x11_window_type;
|
self.platform_specific.x11_window_types = x11_window_types;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
|
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
|
||||||
self.platform_specific.resize_increments = Some(increments.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder {
|
|
||||||
self.platform_specific.base_size = Some(base_size.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
|
|
||||||
self.platform_specific.gtk_theme_variant = Some(variant);
|
self.platform_specific.gtk_theme_variant = Some(variant);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_app_id(mut self, app_id: String) -> WindowBuilder {
|
fn with_resize_increments(mut self, increments: LogicalSize) -> Self {
|
||||||
|
self.platform_specific.resize_increments = Some(increments.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_base_size(mut self, base_size: LogicalSize) -> Self {
|
||||||
|
self.platform_specific.base_size = Some(base_size.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_app_id(mut self, app_id: String) -> Self {
|
||||||
self.platform_specific.app_id = Some(app_id);
|
self.platform_specific.app_id = Some(app_id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ impl EventLoop {
|
||||||
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
||||||
},
|
},
|
||||||
location,
|
location,
|
||||||
|
force: None, // TODO
|
||||||
id: motion.pointer_id as u64,
|
id: motion.pointer_id as u64,
|
||||||
device_id: DEVICE_ID,
|
device_id: DEVICE_ID,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,363 +0,0 @@
|
||||||
#![allow(dead_code, non_camel_case_types, non_snake_case)]
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
use std::mem;
|
|
||||||
use std::os::raw::{c_char, c_double, c_int, c_long, c_ulong, c_ushort, c_void};
|
|
||||||
|
|
||||||
pub type EM_BOOL = c_int;
|
|
||||||
pub type EM_UTF8 = c_char;
|
|
||||||
pub type EMSCRIPTEN_RESULT = c_int;
|
|
||||||
|
|
||||||
pub const EM_TRUE: EM_BOOL = 1;
|
|
||||||
pub const EM_FALSE: EM_BOOL = 0;
|
|
||||||
|
|
||||||
// values for EMSCRIPTEN_RESULT
|
|
||||||
pub const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0;
|
|
||||||
pub const EMSCRIPTEN_RESULT_DEFERRED: c_int = 1;
|
|
||||||
pub const EMSCRIPTEN_RESULT_NOT_SUPPORTED: c_int = -1;
|
|
||||||
pub const EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: c_int = -2;
|
|
||||||
pub const EMSCRIPTEN_RESULT_INVALID_TARGET: c_int = -3;
|
|
||||||
pub const EMSCRIPTEN_RESULT_UNKNOWN_TARGET: c_int = -4;
|
|
||||||
pub const EMSCRIPTEN_RESULT_INVALID_PARAM: c_int = -5;
|
|
||||||
pub const EMSCRIPTEN_RESULT_FAILED: c_int = -6;
|
|
||||||
pub const EMSCRIPTEN_RESULT_NO_DATA: c_int = -7;
|
|
||||||
|
|
||||||
// values for EMSCRIPTEN EVENT
|
|
||||||
pub const EMSCRIPTEN_EVENT_KEYPRESS: c_int = 1;
|
|
||||||
pub const EMSCRIPTEN_EVENT_KEYDOWN: c_int = 2;
|
|
||||||
pub const EMSCRIPTEN_EVENT_KEYUP: c_int = 3;
|
|
||||||
pub const EMSCRIPTEN_EVENT_CLICK: c_int = 4;
|
|
||||||
pub const EMSCRIPTEN_EVENT_MOUSEDOWN: c_int = 5;
|
|
||||||
pub const EMSCRIPTEN_EVENT_MOUSEUP: c_int = 6;
|
|
||||||
pub const EMSCRIPTEN_EVENT_DBLCLICK: c_int = 7;
|
|
||||||
pub const EMSCRIPTEN_EVENT_MOUSEMOVE: c_int = 8;
|
|
||||||
pub const EMSCRIPTEN_EVENT_WHEEL: c_int = 9;
|
|
||||||
pub const EMSCRIPTEN_EVENT_RESIZE: c_int = 10;
|
|
||||||
pub const EMSCRIPTEN_EVENT_SCROLL: c_int = 11;
|
|
||||||
pub const EMSCRIPTEN_EVENT_BLUR: c_int = 12;
|
|
||||||
pub const EMSCRIPTEN_EVENT_FOCUS: c_int = 13;
|
|
||||||
pub const EMSCRIPTEN_EVENT_FOCUSIN: c_int = 14;
|
|
||||||
pub const EMSCRIPTEN_EVENT_FOCUSOUT: c_int = 15;
|
|
||||||
pub const EMSCRIPTEN_EVENT_DEVICEORIENTATION: c_int = 16;
|
|
||||||
pub const EMSCRIPTEN_EVENT_DEVICEMOTION: c_int = 17;
|
|
||||||
pub const EMSCRIPTEN_EVENT_ORIENTATIONCHANGE: c_int = 18;
|
|
||||||
pub const EMSCRIPTEN_EVENT_FULLSCREENCHANGE: c_int = 19;
|
|
||||||
pub const EMSCRIPTEN_EVENT_POINTERLOCKCHANGE: c_int = 20;
|
|
||||||
pub const EMSCRIPTEN_EVENT_VISIBILITYCHANGE: c_int = 21;
|
|
||||||
pub const EMSCRIPTEN_EVENT_TOUCHSTART: c_int = 22;
|
|
||||||
pub const EMSCRIPTEN_EVENT_TOUCHEND: c_int = 23;
|
|
||||||
pub const EMSCRIPTEN_EVENT_TOUCHMOVE: c_int = 24;
|
|
||||||
pub const EMSCRIPTEN_EVENT_TOUCHCANCEL: c_int = 25;
|
|
||||||
pub const EMSCRIPTEN_EVENT_GAMEPADCONNECTED: c_int = 26;
|
|
||||||
pub const EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: c_int = 27;
|
|
||||||
pub const EMSCRIPTEN_EVENT_BEFOREUNLOAD: c_int = 28;
|
|
||||||
pub const EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE: c_int = 29;
|
|
||||||
pub const EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE: c_int = 30;
|
|
||||||
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: c_int = 31;
|
|
||||||
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: c_int = 32;
|
|
||||||
pub const EMSCRIPTEN_EVENT_MOUSEENTER: c_int = 33;
|
|
||||||
pub const EMSCRIPTEN_EVENT_MOUSELEAVE: c_int = 34;
|
|
||||||
pub const EMSCRIPTEN_EVENT_MOUSEOVER: c_int = 35;
|
|
||||||
pub const EMSCRIPTEN_EVENT_MOUSEOUT: c_int = 36;
|
|
||||||
pub const EMSCRIPTEN_EVENT_CANVASRESIZED: c_int = 37;
|
|
||||||
pub const EMSCRIPTEN_EVENT_POINTERLOCKERROR: c_int = 38;
|
|
||||||
|
|
||||||
pub const EM_HTML5_SHORT_STRING_LEN_BYTES: usize = 32;
|
|
||||||
|
|
||||||
pub const DOM_KEY_LOCATION_STANDARD: c_ulong = 0x00;
|
|
||||||
pub const DOM_KEY_LOCATION_LEFT: c_ulong = 0x01;
|
|
||||||
pub const DOM_KEY_LOCATION_RIGHT: c_ulong = 0x02;
|
|
||||||
pub const DOM_KEY_LOCATION_NUMPAD: c_ulong = 0x03;
|
|
||||||
|
|
||||||
pub type em_callback_func = Option<unsafe extern "C" fn()>;
|
|
||||||
|
|
||||||
pub type em_key_callback_func = Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
eventType: c_int,
|
|
||||||
keyEvent: *const EmscriptenKeyboardEvent,
|
|
||||||
userData: *mut c_void,
|
|
||||||
) -> EM_BOOL,
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub type em_mouse_callback_func = Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
eventType: c_int,
|
|
||||||
mouseEvent: *const EmscriptenMouseEvent,
|
|
||||||
userData: *mut c_void,
|
|
||||||
) -> EM_BOOL,
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub type em_pointerlockchange_callback_func = Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
eventType: c_int,
|
|
||||||
pointerlockChangeEvent: *const EmscriptenPointerlockChangeEvent,
|
|
||||||
userData: *mut c_void,
|
|
||||||
) -> EM_BOOL,
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub type em_fullscreenchange_callback_func = Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
eventType: c_int,
|
|
||||||
fullscreenChangeEvent: *const EmscriptenFullscreenChangeEvent,
|
|
||||||
userData: *mut c_void,
|
|
||||||
) -> EM_BOOL,
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub type em_touch_callback_func = Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
eventType: c_int,
|
|
||||||
touchEvent: *const EmscriptenTouchEvent,
|
|
||||||
userData: *mut c_void,
|
|
||||||
) -> EM_BOOL,
|
|
||||||
>;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct EmscriptenFullscreenChangeEvent {
|
|
||||||
pub isFullscreen: c_int,
|
|
||||||
pub fullscreenEnabled: c_int,
|
|
||||||
pub nodeName: [c_char; 128usize],
|
|
||||||
pub id: [c_char; 128usize],
|
|
||||||
pub elementWidth: c_int,
|
|
||||||
pub elementHeight: c_int,
|
|
||||||
pub screenWidth: c_int,
|
|
||||||
pub screenHeight: c_int,
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn bindgen_test_layout_EmscriptenFullscreenChangeEvent() {
|
|
||||||
assert_eq!(mem::size_of::<EmscriptenFullscreenChangeEvent>(), 280usize);
|
|
||||||
assert_eq!(mem::align_of::<EmscriptenFullscreenChangeEvent>(), 4usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy)]
|
|
||||||
pub struct EmscriptenKeyboardEvent {
|
|
||||||
pub key: [c_char; 32usize],
|
|
||||||
pub code: [c_char; 32usize],
|
|
||||||
pub location: c_ulong,
|
|
||||||
pub ctrlKey: c_int,
|
|
||||||
pub shiftKey: c_int,
|
|
||||||
pub altKey: c_int,
|
|
||||||
pub metaKey: c_int,
|
|
||||||
pub repeat: c_int,
|
|
||||||
pub locale: [c_char; 32usize],
|
|
||||||
pub charValue: [c_char; 32usize],
|
|
||||||
pub charCode: c_ulong,
|
|
||||||
pub keyCode: c_ulong,
|
|
||||||
pub which: c_ulong,
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn bindgen_test_layout_EmscriptenKeyboardEvent() {
|
|
||||||
assert_eq!(mem::size_of::<EmscriptenKeyboardEvent>(), 184usize);
|
|
||||||
assert_eq!(mem::align_of::<EmscriptenKeyboardEvent>(), 8usize);
|
|
||||||
}
|
|
||||||
impl Clone for EmscriptenKeyboardEvent {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct EmscriptenMouseEvent {
|
|
||||||
pub timestamp: f64,
|
|
||||||
pub screenX: c_long,
|
|
||||||
pub screenY: c_long,
|
|
||||||
pub clientX: c_long,
|
|
||||||
pub clientY: c_long,
|
|
||||||
pub ctrlKey: c_int,
|
|
||||||
pub shiftKey: c_int,
|
|
||||||
pub altKey: c_int,
|
|
||||||
pub metaKey: c_int,
|
|
||||||
pub button: c_ushort,
|
|
||||||
pub buttons: c_ushort,
|
|
||||||
pub movementX: c_long,
|
|
||||||
pub movementY: c_long,
|
|
||||||
pub targetX: c_long,
|
|
||||||
pub targetY: c_long,
|
|
||||||
pub canvasX: c_long,
|
|
||||||
pub canvasY: c_long,
|
|
||||||
pub padding: c_long,
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn bindgen_test_layout_EmscriptenMouseEvent() {
|
|
||||||
assert_eq!(mem::size_of::<EmscriptenMouseEvent>(), 120usize);
|
|
||||||
assert_eq!(mem::align_of::<EmscriptenMouseEvent>(), 8usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct EmscriptenTouchPoint {
|
|
||||||
pub identifier: c_long,
|
|
||||||
pub screenX: c_long,
|
|
||||||
pub screenY: c_long,
|
|
||||||
pub clientX: c_long,
|
|
||||||
pub clientY: c_long,
|
|
||||||
pub pageX: c_long,
|
|
||||||
pub pageY: c_long,
|
|
||||||
pub isChanged: c_int,
|
|
||||||
pub onTarget: c_int,
|
|
||||||
pub targetX: c_long,
|
|
||||||
pub targetY: c_long,
|
|
||||||
pub canvasX: c_long,
|
|
||||||
pub canvasY: c_long,
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn bindgen_test_layout_EmscriptenTouchPoint() {
|
|
||||||
assert_eq!(mem::size_of::<EmscriptenTouchPoint>(), 96usize);
|
|
||||||
assert_eq!(mem::align_of::<EmscriptenTouchPoint>(), 8usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct EmscriptenTouchEvent {
|
|
||||||
pub numTouches: c_int,
|
|
||||||
pub ctrlKey: c_int,
|
|
||||||
pub shiftKey: c_int,
|
|
||||||
pub altKey: c_int,
|
|
||||||
pub metaKey: c_int,
|
|
||||||
pub touches: [EmscriptenTouchPoint; 32usize],
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn bindgen_test_layout_EmscriptenTouchEvent() {
|
|
||||||
assert_eq!(mem::size_of::<EmscriptenTouchEvent>(), 3096usize);
|
|
||||||
assert_eq!(mem::align_of::<EmscriptenTouchEvent>(), 8usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct EmscriptenPointerlockChangeEvent {
|
|
||||||
pub isActive: c_int,
|
|
||||||
pub nodeName: [c_char; 128usize],
|
|
||||||
pub id: [c_char; 128usize],
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn bindgen_test_layout_EmscriptenPointerlockChangeEvent() {
|
|
||||||
assert_eq!(mem::size_of::<EmscriptenPointerlockChangeEvent>(), 260usize);
|
|
||||||
assert_eq!(mem::align_of::<EmscriptenPointerlockChangeEvent>(), 4usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
pub fn emscripten_set_canvas_size(width: c_int, height: c_int) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_get_canvas_size(
|
|
||||||
width: *mut c_int,
|
|
||||||
height: *mut c_int,
|
|
||||||
is_fullscreen: *mut c_int,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_element_css_size(
|
|
||||||
target: *const c_char,
|
|
||||||
width: c_double,
|
|
||||||
height: c_double,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_get_element_css_size(
|
|
||||||
target: *const c_char,
|
|
||||||
width: *mut c_double,
|
|
||||||
height: *mut c_double,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_request_pointerlock(
|
|
||||||
target: *const c_char,
|
|
||||||
deferUntilInEventHandler: EM_BOOL,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_exit_pointerlock() -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_request_fullscreen(
|
|
||||||
target: *const c_char,
|
|
||||||
deferUntilInEventHandler: EM_BOOL,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_exit_fullscreen() -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_keydown_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: EM_BOOL,
|
|
||||||
callback: em_key_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_keyup_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: EM_BOOL,
|
|
||||||
callback: em_key_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_mousemove_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
user_data: *mut c_void,
|
|
||||||
use_capture: EM_BOOL,
|
|
||||||
callback: em_mouse_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_mousedown_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
user_data: *mut c_void,
|
|
||||||
use_capture: EM_BOOL,
|
|
||||||
callback: em_mouse_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_mouseup_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
user_data: *mut c_void,
|
|
||||||
use_capture: EM_BOOL,
|
|
||||||
callback: em_mouse_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_hide_mouse();
|
|
||||||
|
|
||||||
pub fn emscripten_get_device_pixel_ratio() -> f64;
|
|
||||||
|
|
||||||
pub fn emscripten_set_pointerlockchange_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: EM_BOOL,
|
|
||||||
callback: em_pointerlockchange_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_fullscreenchange_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: EM_BOOL,
|
|
||||||
callback: em_fullscreenchange_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_asm_const(code: *const c_char);
|
|
||||||
|
|
||||||
pub fn emscripten_set_main_loop(
|
|
||||||
func: em_callback_func,
|
|
||||||
fps: c_int,
|
|
||||||
simulate_infinite_loop: EM_BOOL,
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn emscripten_cancel_main_loop();
|
|
||||||
|
|
||||||
pub fn emscripten_set_touchstart_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: c_int,
|
|
||||||
callback: em_touch_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_touchend_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: c_int,
|
|
||||||
callback: em_touch_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_touchmove_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: c_int,
|
|
||||||
callback: em_touch_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
|
|
||||||
pub fn emscripten_set_touchcancel_callback(
|
|
||||||
target: *const c_char,
|
|
||||||
userData: *mut c_void,
|
|
||||||
useCapture: c_int,
|
|
||||||
callback: em_touch_callback_func,
|
|
||||||
) -> EMSCRIPTEN_RESULT;
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -16,15 +16,14 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::platform_impl::platform::{
|
use crate::platform_impl::platform::{
|
||||||
app_state::AppState,
|
app_state,
|
||||||
ffi::{
|
ffi::{
|
||||||
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
|
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
|
||||||
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
|
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
|
||||||
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
||||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
|
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
|
||||||
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
|
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
|
||||||
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSOperatingSystemVersion, NSString,
|
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom,
|
||||||
UIApplicationMain, UIUserInterfaceIdiom,
|
|
||||||
},
|
},
|
||||||
monitor, view, MonitorHandle,
|
monitor, view, MonitorHandle,
|
||||||
};
|
};
|
||||||
|
@ -32,13 +31,6 @@ use crate::platform_impl::platform::{
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
pub struct EventLoopWindowTarget<T: 'static> {
|
||||||
receiver: Receiver<T>,
|
receiver: Receiver<T>,
|
||||||
sender_to_clone: Sender<T>,
|
sender_to_clone: Sender<T>,
|
||||||
capabilities: Capabilities,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopWindowTarget<T> {
|
|
||||||
pub fn capabilities(&self) -> &Capabilities {
|
|
||||||
&self.capabilities
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
pub struct EventLoop<T: 'static> {
|
||||||
|
@ -64,18 +56,11 @@ impl<T: 'static> EventLoop<T> {
|
||||||
// this line sets up the main run loop before `UIApplicationMain`
|
// this line sets up the main run loop before `UIApplicationMain`
|
||||||
setup_control_flow_observers();
|
setup_control_flow_observers();
|
||||||
|
|
||||||
let version: NSOperatingSystemVersion = unsafe {
|
|
||||||
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
|
|
||||||
msg_send![process_info, operatingSystemVersion]
|
|
||||||
};
|
|
||||||
let capabilities = version.into();
|
|
||||||
|
|
||||||
EventLoop {
|
EventLoop {
|
||||||
window_target: RootEventLoopWindowTarget {
|
window_target: RootEventLoopWindowTarget {
|
||||||
p: EventLoopWindowTarget {
|
p: EventLoopWindowTarget {
|
||||||
receiver,
|
receiver,
|
||||||
sender_to_clone,
|
sender_to_clone,
|
||||||
capabilities,
|
|
||||||
},
|
},
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
},
|
},
|
||||||
|
@ -95,7 +80,7 @@ impl<T: 'static> EventLoop<T> {
|
||||||
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
||||||
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
|
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
|
||||||
);
|
);
|
||||||
AppState::will_launch(Box::new(EventLoopHandler {
|
app_state::will_launch(Box::new(EventLoopHandler {
|
||||||
f: event_handler,
|
f: event_handler,
|
||||||
event_loop: self.window_target,
|
event_loop: self.window_target,
|
||||||
}));
|
}));
|
||||||
|
@ -142,8 +127,7 @@ pub struct EventLoopProxy<T> {
|
||||||
source: CFRunLoopSourceRef,
|
source: CFRunLoopSourceRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T> Send for EventLoopProxy<T> {}
|
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
||||||
unsafe impl<T> Sync for EventLoopProxy<T> {}
|
|
||||||
|
|
||||||
impl<T> Clone for EventLoopProxy<T> {
|
impl<T> Clone for EventLoopProxy<T> {
|
||||||
fn clone(&self) -> EventLoopProxy<T> {
|
fn clone(&self) -> EventLoopProxy<T> {
|
||||||
|
@ -163,7 +147,7 @@ impl<T> Drop for EventLoopProxy<T> {
|
||||||
impl<T> EventLoopProxy<T> {
|
impl<T> EventLoopProxy<T> {
|
||||||
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
|
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
// just wakeup the eventloop
|
// just wake up the eventloop
|
||||||
extern "C" fn event_loop_proxy_handler(_: *mut 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
|
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||||
|
@ -171,7 +155,7 @@ impl<T> EventLoopProxy<T> {
|
||||||
let rl = CFRunLoopGetMain();
|
let rl = CFRunLoopGetMain();
|
||||||
// we want all the members of context to be zero/null, except one
|
// we want all the members of context to be zero/null, except one
|
||||||
let mut context: CFRunLoopSourceContext = mem::zeroed();
|
let mut context: CFRunLoopSourceContext = mem::zeroed();
|
||||||
context.perform = event_loop_proxy_handler;
|
context.perform = Some(event_loop_proxy_handler);
|
||||||
let source =
|
let source =
|
||||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
||||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||||
|
@ -204,15 +188,40 @@ fn setup_control_flow_observers() {
|
||||||
unsafe {
|
unsafe {
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
match activity {
|
match activity {
|
||||||
kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(),
|
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
|
||||||
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||||
|
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
||||||
|
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
|
||||||
|
// chosen conservatively to guard against apple using different priorities for their redraw
|
||||||
|
// observers in different OS's or on different devices. If it so happens that it's too
|
||||||
|
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
|
||||||
|
//
|
||||||
|
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||||
|
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||||
|
//
|
||||||
|
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||||
|
extern "C" fn control_flow_main_end_handler(
|
||||||
|
_: CFRunLoopObserverRef,
|
||||||
|
activity: CFRunLoopActivity,
|
||||||
|
_: *mut c_void,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
match activity {
|
||||||
|
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
|
||||||
|
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||||
// without that, LoopDestroyed will get sent after EventsCleared
|
|
||||||
extern "C" fn control_flow_end_handler(
|
extern "C" fn control_flow_end_handler(
|
||||||
_: CFRunLoopObserverRef,
|
_: CFRunLoopObserverRef,
|
||||||
activity: CFRunLoopActivity,
|
activity: CFRunLoopActivity,
|
||||||
|
@ -221,7 +230,7 @@ fn setup_control_flow_observers() {
|
||||||
unsafe {
|
unsafe {
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
match activity {
|
match activity {
|
||||||
kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(),
|
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
|
||||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -229,6 +238,7 @@ fn setup_control_flow_observers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let main_loop = CFRunLoopGetMain();
|
let main_loop = CFRunLoopGetMain();
|
||||||
|
|
||||||
let begin_observer = CFRunLoopObserverCreate(
|
let begin_observer = CFRunLoopObserverCreate(
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
||||||
|
@ -238,6 +248,17 @@ fn setup_control_flow_observers() {
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
);
|
);
|
||||||
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
||||||
|
|
||||||
|
let main_end_observer = CFRunLoopObserverCreate(
|
||||||
|
ptr::null_mut(),
|
||||||
|
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||||
|
1, // repeat = true
|
||||||
|
0, // see comment on `control_flow_main_end_handler`
|
||||||
|
control_flow_main_end_handler,
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
|
||||||
|
|
||||||
let end_observer = CFRunLoopObserverCreate(
|
let end_observer = CFRunLoopObserverCreate(
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||||
|
@ -297,20 +318,3 @@ pub unsafe fn get_idiom() -> Idiom {
|
||||||
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
|
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
|
||||||
raw_idiom.into()
|
raw_idiom.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Capabilities {
|
|
||||||
pub supports_safe_area: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<NSOperatingSystemVersion> for Capabilities {
|
|
||||||
fn from(os_version: NSOperatingSystemVersion) -> Capabilities {
|
|
||||||
assert!(
|
|
||||||
os_version.major >= 8,
|
|
||||||
"`winit` current requires iOS version 8 or greater"
|
|
||||||
);
|
|
||||||
|
|
||||||
let supports_safe_area = os_version.major >= 11;
|
|
||||||
|
|
||||||
Capabilities { supports_safe_area }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||||
|
|
||||||
use std::{ffi::CString, ops::BitOr, os::raw::*};
|
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
|
||||||
|
|
||||||
use objc::{runtime::Object, Encode, Encoding};
|
use objc::{runtime::Object, Encode, Encoding};
|
||||||
|
|
||||||
use crate::platform::ios::{Idiom, ValidOrientations};
|
use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
|
||||||
|
|
||||||
pub type id = *mut Object;
|
pub type id = *mut Object;
|
||||||
pub const nil: id = 0 as id;
|
pub const nil: id = 0 as id;
|
||||||
|
@ -70,6 +70,24 @@ pub enum UITouchPhase {
|
||||||
Cancelled,
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[repr(isize)]
|
||||||
|
pub enum UIForceTouchCapability {
|
||||||
|
Unknown = 0,
|
||||||
|
Unavailable,
|
||||||
|
Available,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[repr(isize)]
|
||||||
|
pub enum UITouchType {
|
||||||
|
Direct = 0,
|
||||||
|
Indirect,
|
||||||
|
Pencil,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UIEdgeInsets {
|
pub struct UIEdgeInsets {
|
||||||
|
@ -173,6 +191,51 @@ impl UIInterfaceOrientationMask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct UIRectEdge(NSUInteger);
|
||||||
|
|
||||||
|
unsafe impl Encode for UIRectEdge {
|
||||||
|
fn encode() -> Encoding {
|
||||||
|
NSUInteger::encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ScreenEdge> for UIRectEdge {
|
||||||
|
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
|
||||||
|
assert_eq!(
|
||||||
|
screen_edge.bits() & !ScreenEdge::ALL.bits(),
|
||||||
|
0,
|
||||||
|
"invalid `ScreenEdge`"
|
||||||
|
);
|
||||||
|
UIRectEdge(screen_edge.bits().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<ScreenEdge> for UIRectEdge {
|
||||||
|
fn into(self) -> ScreenEdge {
|
||||||
|
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
|
||||||
|
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 = "UIKit", kind = "framework")]
|
||||||
#[link(name = "CoreFoundation", kind = "framework")]
|
#[link(name = "CoreFoundation", kind = "framework")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -268,14 +331,14 @@ pub enum CFRunLoopTimerContext {}
|
||||||
pub struct CFRunLoopSourceContext {
|
pub struct CFRunLoopSourceContext {
|
||||||
pub version: CFIndex,
|
pub version: CFIndex,
|
||||||
pub info: *mut c_void,
|
pub info: *mut c_void,
|
||||||
pub retain: extern "C" fn(*const c_void) -> *const c_void,
|
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
|
||||||
pub release: extern "C" fn(*const c_void),
|
pub release: Option<extern "C" fn(*const c_void)>,
|
||||||
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
|
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
|
||||||
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
|
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
|
||||||
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
|
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
|
||||||
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
||||||
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
||||||
pub perform: extern "C" fn(*mut c_void),
|
pub perform: Option<extern "C" fn(*mut c_void)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NSString: Sized {
|
pub trait NSString: Sized {
|
||||||
|
|
|
@ -79,7 +79,7 @@ use std::fmt;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,98 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashSet, VecDeque},
|
collections::{BTreeSet, VecDeque},
|
||||||
fmt,
|
fmt,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform_impl::platform::{
|
||||||
|
app_state,
|
||||||
|
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::platform_impl::platform::ffi::{
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger,
|
pub struct VideoMode {
|
||||||
};
|
pub(crate) size: (u32, u32),
|
||||||
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) screen_mode: id,
|
||||||
|
pub(crate) monitor: MonitorHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for VideoMode {
|
||||||
|
fn clone(&self) -> VideoMode {
|
||||||
|
VideoMode {
|
||||||
|
size: self.size,
|
||||||
|
bit_depth: self.bit_depth,
|
||||||
|
refresh_rate: self.refresh_rate,
|
||||||
|
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
|
||||||
|
monitor: self.monitor.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VideoMode {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
|
||||||
|
let () = msg_send![self.screen_mode, release];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl 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 os_capabilities = app_state::os_capabilities();
|
||||||
|
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
|
||||||
|
msg_send![uiscreen, maximumFramesPerSecond]
|
||||||
|
} else {
|
||||||
|
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
||||||
|
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
||||||
|
//
|
||||||
|
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
|
||||||
|
// supported, they are all guaranteed to have 60hz refresh rates. This does not
|
||||||
|
// correctly handle external displays. ProMotion displays support 120fps, but they were
|
||||||
|
// introduced at the same time as the `maximumFramesPerSecond` API.
|
||||||
|
//
|
||||||
|
// FIXME: earlier OSs could calculate the refresh rate using
|
||||||
|
// `-[CADisplayLink duration]`.
|
||||||
|
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
||||||
|
60
|
||||||
|
};
|
||||||
|
let size: CGSize = msg_send![screen_mode, size];
|
||||||
|
VideoMode {
|
||||||
|
size: (size.width as u32, size.height as u32),
|
||||||
|
bit_depth: 32,
|
||||||
|
refresh_rate: refresh_rate as u16,
|
||||||
|
screen_mode: msg_send![screen_mode, retain],
|
||||||
|
monitor: MonitorHandle::retained_new(uiscreen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
self.bit_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
self.refresh_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
RootMonitorHandle {
|
||||||
|
inner: self.monitor.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct Inner {
|
pub struct Inner {
|
||||||
uiscreen: id,
|
uiscreen: id,
|
||||||
}
|
}
|
||||||
|
@ -25,6 +105,7 @@ impl Drop for Inner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
inner: Inner,
|
inner: Inner,
|
||||||
}
|
}
|
||||||
|
@ -106,9 +187,10 @@ impl MonitorHandle {
|
||||||
impl Inner {
|
impl Inner {
|
||||||
pub fn name(&self) -> Option<String> {
|
pub fn name(&self) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if self.uiscreen == main_uiscreen().uiscreen {
|
let main = main_uiscreen();
|
||||||
|
if self.uiscreen == main.uiscreen {
|
||||||
Some("Primary".to_string())
|
Some("Primary".to_string())
|
||||||
} else if self.uiscreen == mirrored_uiscreen().uiscreen {
|
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
||||||
Some("Mirrored".to_string())
|
Some("Mirrored".to_string())
|
||||||
} else {
|
} else {
|
||||||
uiscreens()
|
uiscreens()
|
||||||
|
@ -140,22 +222,18 @@ impl Inner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
|
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];
|
||||||
|
|
||||||
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
|
for i in 0..available_mode_count {
|
||||||
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
|
let mode: id = msg_send![available_modes, objectAtIndex: i];
|
||||||
|
modes.insert(RootVideoMode {
|
||||||
let mut modes = HashSet::with_capacity(available_mode_count);
|
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
||||||
|
});
|
||||||
for i in 0..available_mode_count {
|
}
|
||||||
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
|
|
||||||
let size: CGSize = unsafe { msg_send![mode, size] };
|
|
||||||
modes.insert(VideoMode {
|
|
||||||
size: (size.width as u32, size.height as u32),
|
|
||||||
bit_depth: 32,
|
|
||||||
refresh_rate: refresh_rate as u16,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modes.into_iter()
|
modes.into_iter()
|
||||||
|
@ -167,6 +245,15 @@ impl Inner {
|
||||||
pub fn ui_screen(&self) -> id {
|
pub fn ui_screen(&self) -> id {
|
||||||
self.uiscreen
|
self.uiscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// requires being run on main thread
|
// requires being run on main thread
|
||||||
|
@ -176,8 +263,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// requires being run on main thread
|
// requires being run on main thread
|
||||||
unsafe fn mirrored_uiscreen() -> MonitorHandle {
|
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
||||||
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen];
|
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
||||||
MonitorHandle::retained_new(uiscreen)
|
MonitorHandle::retained_new(uiscreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,78 @@ use objc::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent},
|
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||||
platform::ios::MonitorHandleExtIOS,
|
platform::ios::MonitorHandleExtIOS,
|
||||||
window::{WindowAttributes, WindowId as RootWindowId},
|
platform_impl::platform::{
|
||||||
|
app_state::{self, OSCapabilities},
|
||||||
|
event_loop,
|
||||||
|
ffi::{
|
||||||
|
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
|
||||||
|
UIRectEdge, UITouchPhase, UITouchType,
|
||||||
|
},
|
||||||
|
window::PlatformSpecificWindowBuilderAttributes,
|
||||||
|
DeviceId,
|
||||||
|
},
|
||||||
|
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::platform_impl::platform::{
|
macro_rules! add_property {
|
||||||
app_state::AppState,
|
(
|
||||||
event_loop,
|
$decl:ident,
|
||||||
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase},
|
$name:ident: $t:ty,
|
||||||
window::PlatformSpecificWindowBuilderAttributes,
|
$setter_name:ident: |$object:ident| $after_set:expr,
|
||||||
DeviceId,
|
$getter_name:ident,
|
||||||
};
|
) => {
|
||||||
|
add_property!(
|
||||||
|
$decl,
|
||||||
|
$name: $t,
|
||||||
|
$setter_name: true, |_, _|{}; |$object| $after_set,
|
||||||
|
$getter_name,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
(
|
||||||
|
$decl:ident,
|
||||||
|
$name:ident: $t:ty,
|
||||||
|
$setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr,
|
||||||
|
$getter_name:ident,
|
||||||
|
) => {
|
||||||
|
{
|
||||||
|
const VAR_NAME: &'static str = concat!("_", stringify!($name));
|
||||||
|
$decl.add_ivar::<$t>(VAR_NAME);
|
||||||
|
let setter = if $capability {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
||||||
|
unsafe {
|
||||||
|
$object.set_ivar::<$t>(VAR_NAME, value);
|
||||||
|
}
|
||||||
|
$after_set
|
||||||
|
}
|
||||||
|
$setter_name
|
||||||
|
} else {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
||||||
|
unsafe {
|
||||||
|
$object.set_ivar::<$t>(VAR_NAME, value);
|
||||||
|
}
|
||||||
|
$err(&app_state::os_capabilities(), "ignoring")
|
||||||
|
}
|
||||||
|
$setter_name
|
||||||
|
};
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
extern "C" fn $getter_name($object: &Object, _: Sel) -> $t {
|
||||||
|
unsafe { *$object.get_ivar::<$t>(VAR_NAME) }
|
||||||
|
}
|
||||||
|
$decl.add_method(
|
||||||
|
sel!($setter_name:),
|
||||||
|
setter as extern "C" fn(&mut Object, Sel, $t),
|
||||||
|
);
|
||||||
|
$decl.add_method(
|
||||||
|
sel!($getter_name),
|
||||||
|
$getter_name as extern "C" fn(&Object, Sel) -> $t,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// requires main thread
|
// requires main thread
|
||||||
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||||
|
@ -41,7 +101,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||||
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
|
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let window: id = msg_send![object, window];
|
let window: id = msg_send![object, window];
|
||||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
assert!(!window.is_null());
|
||||||
|
app_state::handle_nonuser_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(window.into()),
|
window_id: RootWindowId(window.into()),
|
||||||
event: WindowEvent::RedrawRequested,
|
event: WindowEvent::RedrawRequested,
|
||||||
});
|
});
|
||||||
|
@ -52,22 +113,143 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||||
|
|
||||||
extern "C" fn layout_subviews(object: &Object, _: Sel) {
|
extern "C" fn layout_subviews(object: &Object, _: Sel) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
let superclass: &'static Class = msg_send![object, superclass];
|
||||||
|
let () = msg_send![super(object, superclass), layoutSubviews];
|
||||||
|
|
||||||
let window: id = msg_send![object, window];
|
let window: id = msg_send![object, window];
|
||||||
|
assert!(!window.is_null());
|
||||||
let bounds: CGRect = msg_send![window, bounds];
|
let bounds: CGRect = msg_send![window, bounds];
|
||||||
let screen: id = msg_send![window, screen];
|
let screen: id = msg_send![window, screen];
|
||||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||||
let screen_frame: CGRect =
|
let screen_frame: CGRect =
|
||||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
||||||
let size = crate::dpi::LogicalSize {
|
let size = crate::dpi::LogicalSize {
|
||||||
width: screen_frame.size.width,
|
width: screen_frame.size.width as _,
|
||||||
height: screen_frame.size.height,
|
height: screen_frame.size.height as _,
|
||||||
};
|
};
|
||||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
app_state::handle_nonuser_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(window.into()),
|
window_id: RootWindowId(window.into()),
|
||||||
event: WindowEvent::Resized(size),
|
event: WindowEvent::Resized(size),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn set_content_scale_factor(
|
||||||
|
object: &mut Object,
|
||||||
|
_: Sel,
|
||||||
|
untrusted_hidpi_factor: CGFloat,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
let superclass: &'static Class = msg_send![object, superclass];
|
let superclass: &'static Class = msg_send![object, superclass];
|
||||||
let () = msg_send![super(object, superclass), layoutSubviews];
|
let () = msg_send![
|
||||||
|
super(object, superclass),
|
||||||
|
setContentScaleFactor: untrusted_hidpi_factor
|
||||||
|
];
|
||||||
|
|
||||||
|
let window: id = msg_send![object, window];
|
||||||
|
// `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow
|
||||||
|
// makeKeyAndVisible]` at window creation time (either manually or internally by
|
||||||
|
// UIKit when the `UIView` is first created), in which case we send no events here
|
||||||
|
if window.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
|
||||||
|
// content scale factor to a device-specific default value", so we can't use the
|
||||||
|
// parameter here. We can query the actual factor using the getter
|
||||||
|
let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor];
|
||||||
|
assert!(
|
||||||
|
!hidpi_factor.is_nan()
|
||||||
|
&& hidpi_factor.is_finite()
|
||||||
|
&& hidpi_factor.is_sign_positive()
|
||||||
|
&& hidpi_factor > 0.0,
|
||||||
|
"invalid hidpi_factor set on UIView",
|
||||||
|
);
|
||||||
|
let bounds: CGRect = msg_send![object, bounds];
|
||||||
|
let screen: id = msg_send![window, screen];
|
||||||
|
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||||
|
let screen_frame: CGRect =
|
||||||
|
msg_send![object, 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(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(window.into()),
|
||||||
|
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
|
||||||
|
})
|
||||||
|
.chain(std::iter::once(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(window.into()),
|
||||||
|
event: WindowEvent::Resized(size),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
|
||||||
|
unsafe {
|
||||||
|
let window: id = msg_send![object, window];
|
||||||
|
assert!(!window.is_null());
|
||||||
|
let uiscreen: id = msg_send![window, screen];
|
||||||
|
let touches_enum: id = msg_send![touches, objectEnumerator];
|
||||||
|
let mut touch_events = Vec::new();
|
||||||
|
let os_supports_force = app_state::os_capabilities().force_touch;
|
||||||
|
loop {
|
||||||
|
let touch: id = msg_send![touches_enum, nextObject];
|
||||||
|
if touch == nil {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let location: CGPoint = msg_send![touch, locationInView: nil];
|
||||||
|
let touch_type: UITouchType = msg_send![touch, type];
|
||||||
|
let force = if os_supports_force {
|
||||||
|
let trait_collection: id = msg_send![object, traitCollection];
|
||||||
|
let touch_capability: UIForceTouchCapability =
|
||||||
|
msg_send![trait_collection, forceTouchCapability];
|
||||||
|
// Both the OS _and_ the device need to be checked for force touch support.
|
||||||
|
if touch_capability == UIForceTouchCapability::Available {
|
||||||
|
let force: CGFloat = msg_send![touch, force];
|
||||||
|
let max_possible_force: CGFloat =
|
||||||
|
msg_send![touch, maximumPossibleForce];
|
||||||
|
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
|
||||||
|
let angle: CGFloat = msg_send![touch, altitudeAngle];
|
||||||
|
Some(angle as _)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Some(Force::Calibrated {
|
||||||
|
force: force as _,
|
||||||
|
max_possible_force: max_possible_force as _,
|
||||||
|
altitude_angle,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let touch_id = touch as u64;
|
||||||
|
let phase: UITouchPhase = msg_send![touch, phase];
|
||||||
|
let phase = match phase {
|
||||||
|
UITouchPhase::Began => TouchPhase::Started,
|
||||||
|
UITouchPhase::Moved => TouchPhase::Moved,
|
||||||
|
// 2 is UITouchPhase::Stationary and is not expected here
|
||||||
|
UITouchPhase::Ended => TouchPhase::Ended,
|
||||||
|
UITouchPhase::Cancelled => TouchPhase::Cancelled,
|
||||||
|
_ => panic!("unexpected touch phase: {:?}", phase as i32),
|
||||||
|
};
|
||||||
|
|
||||||
|
touch_events.push(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(window.into()),
|
||||||
|
event: WindowEvent::Touch(Touch {
|
||||||
|
device_id: RootDeviceId(DeviceId { uiscreen }),
|
||||||
|
id: touch_id,
|
||||||
|
location: (location.x as f64, location.y as f64).into(),
|
||||||
|
force,
|
||||||
|
phase,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
app_state::handle_nonuser_events(touch_events);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,184 +264,9 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||||
sel!(layoutSubviews),
|
sel!(layoutSubviews),
|
||||||
layout_subviews as extern "C" fn(&Object, Sel),
|
layout_subviews as extern "C" fn(&Object, Sel),
|
||||||
);
|
);
|
||||||
decl.register()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn get_view_controller_class() -> &'static Class {
|
|
||||||
static mut CLASS: Option<&'static Class> = None;
|
|
||||||
if CLASS.is_none() {
|
|
||||||
let uiviewcontroller_class = class!(UIViewController);
|
|
||||||
|
|
||||||
extern "C" fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) {
|
|
||||||
unsafe {
|
|
||||||
object.set_ivar::<BOOL>("_prefers_status_bar_hidden", hidden);
|
|
||||||
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL {
|
|
||||||
unsafe { *object.get_ivar::<BOOL>("_prefers_status_bar_hidden") }
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn set_supported_orientations(
|
|
||||||
object: &mut Object,
|
|
||||||
_: Sel,
|
|
||||||
orientations: UIInterfaceOrientationMask,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
object.set_ivar::<UIInterfaceOrientationMask>(
|
|
||||||
"_supported_orientations",
|
|
||||||
orientations,
|
|
||||||
);
|
|
||||||
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn supported_orientations(
|
|
||||||
object: &Object,
|
|
||||||
_: Sel,
|
|
||||||
) -> UIInterfaceOrientationMask {
|
|
||||||
unsafe { *object.get_ivar::<UIInterfaceOrientationMask>("_supported_orientations") }
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
|
|
||||||
YES
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
|
|
||||||
.expect("Failed to declare class `WinitUIViewController`");
|
|
||||||
decl.add_ivar::<BOOL>("_prefers_status_bar_hidden");
|
|
||||||
decl.add_ivar::<UIInterfaceOrientationMask>("_supported_orientations");
|
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(setPrefersStatusBarHidden:),
|
sel!(setContentScaleFactor:),
|
||||||
set_prefers_status_bar_hidden as extern "C" fn(&mut Object, Sel, BOOL),
|
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(prefersStatusBarHidden),
|
|
||||||
prefers_status_bar_hidden as extern "C" fn(&Object, Sel) -> BOOL,
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(setSupportedInterfaceOrientations:),
|
|
||||||
set_supported_orientations
|
|
||||||
as extern "C" fn(&mut Object, Sel, UIInterfaceOrientationMask),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(supportedInterfaceOrientations),
|
|
||||||
supported_orientations as extern "C" fn(&Object, Sel) -> UIInterfaceOrientationMask,
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(shouldAutorotate),
|
|
||||||
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
|
|
||||||
);
|
|
||||||
CLASS = Some(decl.register());
|
|
||||||
}
|
|
||||||
CLASS.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn get_window_class() -> &'static Class {
|
|
||||||
static mut CLASS: Option<&'static Class> = None;
|
|
||||||
if CLASS.is_none() {
|
|
||||||
let uiwindow_class = class!(UIWindow);
|
|
||||||
|
|
||||||
extern "C" fn become_key_window(object: &Object, _: Sel) {
|
|
||||||
unsafe {
|
|
||||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(object.into()),
|
|
||||||
event: WindowEvent::Focused(true),
|
|
||||||
});
|
|
||||||
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn resign_key_window(object: &Object, _: Sel) {
|
|
||||||
unsafe {
|
|
||||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(object.into()),
|
|
||||||
event: WindowEvent::Focused(false),
|
|
||||||
});
|
|
||||||
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
|
|
||||||
unsafe {
|
|
||||||
let uiscreen = msg_send![object, screen];
|
|
||||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
|
||||||
let mut touch_events = Vec::new();
|
|
||||||
loop {
|
|
||||||
let touch: id = msg_send![touches_enum, nextObject];
|
|
||||||
if touch == nil {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let location: CGPoint = msg_send![touch, locationInView: nil];
|
|
||||||
let touch_id = touch as u64;
|
|
||||||
let phase: UITouchPhase = msg_send![touch, phase];
|
|
||||||
let phase = match phase {
|
|
||||||
UITouchPhase::Began => TouchPhase::Started,
|
|
||||||
UITouchPhase::Moved => TouchPhase::Moved,
|
|
||||||
// 2 is UITouchPhase::Stationary and is not expected here
|
|
||||||
UITouchPhase::Ended => TouchPhase::Ended,
|
|
||||||
UITouchPhase::Cancelled => TouchPhase::Cancelled,
|
|
||||||
_ => panic!("unexpected touch phase: {:?}", phase as i32),
|
|
||||||
};
|
|
||||||
|
|
||||||
touch_events.push(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(object.into()),
|
|
||||||
event: WindowEvent::Touch(Touch {
|
|
||||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
|
||||||
id: touch_id,
|
|
||||||
location: (location.x as f64, location.y as f64).into(),
|
|
||||||
phase,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
AppState::handle_nonuser_events(touch_events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) {
|
|
||||||
unsafe {
|
|
||||||
let () = msg_send![
|
|
||||||
super(object, class!(UIWindow)),
|
|
||||||
setContentScaleFactor: hidpi_factor
|
|
||||||
];
|
|
||||||
let view_controller: id = msg_send![object, rootViewController];
|
|
||||||
let view: id = msg_send![view_controller, view];
|
|
||||||
let () = msg_send![view, setContentScaleFactor: hidpi_factor];
|
|
||||||
let bounds: CGRect = msg_send![object, bounds];
|
|
||||||
let screen: id = msg_send![object, screen];
|
|
||||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
|
||||||
let screen_frame: CGRect =
|
|
||||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
|
||||||
let size = crate::dpi::LogicalSize {
|
|
||||||
width: screen_frame.size.width,
|
|
||||||
height: screen_frame.size.height,
|
|
||||||
};
|
|
||||||
AppState::handle_nonuser_events(
|
|
||||||
std::iter::once(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(object.into()),
|
|
||||||
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
|
|
||||||
})
|
|
||||||
.chain(std::iter::once(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(object.into()),
|
|
||||||
event: WindowEvent::Resized(size),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
|
|
||||||
.expect("Failed to declare class `WinitUIWindow`");
|
|
||||||
decl.add_method(
|
|
||||||
sel!(becomeKeyWindow),
|
|
||||||
become_key_window as extern "C" fn(&Object, Sel),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(resignKeyWindow),
|
|
||||||
resign_key_window as extern "C" fn(&Object, Sel),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
|
@ -279,9 +286,114 @@ unsafe fn get_window_class() -> &'static Class {
|
||||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
decl.register()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires main thread
|
||||||
|
unsafe fn get_view_controller_class() -> &'static Class {
|
||||||
|
static mut CLASS: Option<&'static Class> = None;
|
||||||
|
if CLASS.is_none() {
|
||||||
|
let os_capabilities = app_state::os_capabilities();
|
||||||
|
|
||||||
|
let uiviewcontroller_class = class!(UIViewController);
|
||||||
|
|
||||||
|
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
|
||||||
|
YES
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
|
||||||
|
.expect("Failed to declare class `WinitUIViewController`");
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(setContentScaleFactor:),
|
sel!(shouldAutorotate),
|
||||||
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
|
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
|
||||||
|
);
|
||||||
|
add_property! {
|
||||||
|
decl,
|
||||||
|
prefers_status_bar_hidden: BOOL,
|
||||||
|
setPrefersStatusBarHidden: |object| {
|
||||||
|
unsafe {
|
||||||
|
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prefersStatusBarHidden,
|
||||||
|
}
|
||||||
|
add_property! {
|
||||||
|
decl,
|
||||||
|
prefers_home_indicator_auto_hidden: BOOL,
|
||||||
|
setPrefersHomeIndicatorAutoHidden:
|
||||||
|
os_capabilities.home_indicator_hidden,
|
||||||
|
OSCapabilities::home_indicator_hidden_err_msg;
|
||||||
|
|object| {
|
||||||
|
unsafe {
|
||||||
|
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prefersHomeIndicatorAutoHidden,
|
||||||
|
}
|
||||||
|
add_property! {
|
||||||
|
decl,
|
||||||
|
supported_orientations: UIInterfaceOrientationMask,
|
||||||
|
setSupportedInterfaceOrientations: |object| {
|
||||||
|
unsafe {
|
||||||
|
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportedInterfaceOrientations,
|
||||||
|
}
|
||||||
|
add_property! {
|
||||||
|
decl,
|
||||||
|
preferred_screen_edges_deferring_system_gestures: UIRectEdge,
|
||||||
|
setPreferredScreenEdgesDeferringSystemGestures:
|
||||||
|
os_capabilities.defer_system_gestures,
|
||||||
|
OSCapabilities::defer_system_gestures_err_msg;
|
||||||
|
|object| {
|
||||||
|
unsafe {
|
||||||
|
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preferredScreenEdgesDeferringSystemGestures,
|
||||||
|
}
|
||||||
|
CLASS = Some(decl.register());
|
||||||
|
}
|
||||||
|
CLASS.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires main thread
|
||||||
|
unsafe fn get_window_class() -> &'static Class {
|
||||||
|
static mut CLASS: Option<&'static Class> = None;
|
||||||
|
if CLASS.is_none() {
|
||||||
|
let uiwindow_class = class!(UIWindow);
|
||||||
|
|
||||||
|
extern "C" fn become_key_window(object: &Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
app_state::handle_nonuser_event(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(object.into()),
|
||||||
|
event: WindowEvent::Focused(true),
|
||||||
|
});
|
||||||
|
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn resign_key_window(object: &Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
app_state::handle_nonuser_event(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(object.into()),
|
||||||
|
event: WindowEvent::Focused(false),
|
||||||
|
});
|
||||||
|
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
|
||||||
|
.expect("Failed to declare class `WinitUIWindow`");
|
||||||
|
decl.add_method(
|
||||||
|
sel!(becomeKeyWindow),
|
||||||
|
become_key_window as extern "C" fn(&Object, Sel),
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(resignKeyWindow),
|
||||||
|
resign_key_window as extern "C" fn(&Object, Sel),
|
||||||
);
|
);
|
||||||
|
|
||||||
CLASS = Some(decl.register());
|
CLASS = Some(decl.register());
|
||||||
|
@ -302,13 +414,16 @@ pub unsafe fn create_view(
|
||||||
let view: id = msg_send![view, initWithFrame: frame];
|
let view: id = msg_send![view, initWithFrame: frame];
|
||||||
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
|
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
|
||||||
let () = msg_send![view, setMultipleTouchEnabled: YES];
|
let () = msg_send![view, setMultipleTouchEnabled: YES];
|
||||||
|
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
|
||||||
|
let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat];
|
||||||
|
}
|
||||||
|
|
||||||
view
|
view
|
||||||
}
|
}
|
||||||
|
|
||||||
// requires main thread
|
// requires main thread
|
||||||
pub unsafe fn create_view_controller(
|
pub unsafe fn create_view_controller(
|
||||||
window_attributes: &WindowAttributes,
|
_window_attributes: &WindowAttributes,
|
||||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||||
view: id,
|
view: id,
|
||||||
) -> id {
|
) -> id {
|
||||||
|
@ -324,16 +439,24 @@ pub unsafe fn create_view_controller(
|
||||||
!view_controller.is_null(),
|
!view_controller.is_null(),
|
||||||
"Failed to initialize `UIViewController` instance"
|
"Failed to initialize `UIViewController` instance"
|
||||||
);
|
);
|
||||||
let status_bar_hidden = if window_attributes.decorations {
|
let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden {
|
||||||
NO
|
|
||||||
} else {
|
|
||||||
YES
|
YES
|
||||||
|
} else {
|
||||||
|
NO
|
||||||
};
|
};
|
||||||
let idiom = event_loop::get_idiom();
|
let idiom = event_loop::get_idiom();
|
||||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
||||||
platform_attributes.valid_orientations,
|
platform_attributes.valid_orientations,
|
||||||
idiom,
|
idiom,
|
||||||
);
|
);
|
||||||
|
let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden {
|
||||||
|
YES
|
||||||
|
} else {
|
||||||
|
NO
|
||||||
|
};
|
||||||
|
let edges: UIRectEdge = platform_attributes
|
||||||
|
.preferred_screen_edges_deferring_system_gestures
|
||||||
|
.into();
|
||||||
let () = msg_send![
|
let () = msg_send![
|
||||||
view_controller,
|
view_controller,
|
||||||
setPrefersStatusBarHidden: status_bar_hidden
|
setPrefersStatusBarHidden: status_bar_hidden
|
||||||
|
@ -342,6 +465,14 @@ pub unsafe fn create_view_controller(
|
||||||
view_controller,
|
view_controller,
|
||||||
setSupportedInterfaceOrientations: supported_orientations
|
setSupportedInterfaceOrientations: supported_orientations
|
||||||
];
|
];
|
||||||
|
let () = msg_send![
|
||||||
|
view_controller,
|
||||||
|
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
||||||
|
];
|
||||||
|
let () = msg_send![
|
||||||
|
view_controller,
|
||||||
|
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||||
|
];
|
||||||
let () = msg_send![view_controller, setView: view];
|
let () = msg_send![view_controller, setView: view];
|
||||||
view_controller
|
view_controller
|
||||||
}
|
}
|
||||||
|
@ -349,7 +480,7 @@ pub unsafe fn create_view_controller(
|
||||||
// requires main thread
|
// requires main thread
|
||||||
pub unsafe fn create_window(
|
pub unsafe fn create_window(
|
||||||
window_attributes: &WindowAttributes,
|
window_attributes: &WindowAttributes,
|
||||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||||
frame: CGRect,
|
frame: CGRect,
|
||||||
view_controller: id,
|
view_controller: id,
|
||||||
) -> id {
|
) -> id {
|
||||||
|
@ -363,11 +494,16 @@ pub unsafe fn create_window(
|
||||||
"Failed to initialize `UIWindow` instance"
|
"Failed to initialize `UIWindow` instance"
|
||||||
);
|
);
|
||||||
let () = msg_send![window, setRootViewController: view_controller];
|
let () = msg_send![window, setRootViewController: view_controller];
|
||||||
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
|
match window_attributes.fullscreen {
|
||||||
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
|
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||||
}
|
let uiscreen = video_mode.monitor().ui_screen() as id;
|
||||||
if let &Some(ref monitor) = &window_attributes.fullscreen {
|
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||||
let () = msg_send![window, setScreen:monitor.ui_screen()];
|
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
|
||||||
|
}
|
||||||
|
Some(Fullscreen::Borderless(ref monitor)) => {
|
||||||
|
msg_send![window, setScreen:monitor.ui_screen()]
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
window
|
window
|
||||||
|
@ -376,17 +512,17 @@ pub unsafe fn create_window(
|
||||||
pub fn create_delegate_class() {
|
pub fn create_delegate_class() {
|
||||||
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
||||||
unsafe {
|
unsafe {
|
||||||
AppState::did_finish_launching();
|
app_state::did_finish_launching();
|
||||||
}
|
}
|
||||||
YES
|
YES
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||||
unsafe { AppState::handle_nonuser_event(Event::Resumed) }
|
unsafe { app_state::handle_nonuser_event(Event::Resumed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||||
unsafe { AppState::handle_nonuser_event(Event::Suspended) }
|
unsafe { app_state::handle_nonuser_event(Event::Suspended) }
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
|
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
|
||||||
|
@ -411,8 +547,8 @@ pub fn create_delegate_class() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppState::handle_nonuser_events(events);
|
app_state::handle_nonuser_events(events);
|
||||||
AppState::terminated();
|
app_state::terminated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,34 @@
|
||||||
|
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
use objc::runtime::{Class, Object, NO, YES};
|
use objc::runtime::{Class, Object, BOOL, NO, YES};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{self, LogicalPosition, LogicalSize},
|
dpi::{self, LogicalPosition, LogicalSize},
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
|
event::{Event, WindowEvent},
|
||||||
icon::Icon,
|
icon::Icon,
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
monitor::MonitorHandle as RootMonitorHandle,
|
||||||
platform::ios::{MonitorHandleExtIOS, ValidOrientations},
|
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
|
||||||
platform_impl::platform::{
|
platform_impl::platform::{
|
||||||
app_state::AppState,
|
app_state, event_loop,
|
||||||
event_loop,
|
ffi::{
|
||||||
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask},
|
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
|
||||||
|
UIRectEdge, UIScreenOverscanCompensation,
|
||||||
|
},
|
||||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Inner {
|
pub struct Inner {
|
||||||
pub window: id,
|
pub window: id,
|
||||||
pub view_controller: id,
|
pub view_controller: id,
|
||||||
pub view: id,
|
pub view: id,
|
||||||
supports_safe_area: bool,
|
gl_or_metal_backed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Inner {
|
impl Drop for Inner {
|
||||||
|
@ -55,7 +59,19 @@ impl Inner {
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
pub fn request_redraw(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let () = msg_send![self.view, setNeedsDisplay];
|
if self.gl_or_metal_backed {
|
||||||
|
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
|
||||||
|
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
|
||||||
|
// raw or gl/metal for drawing this work is completely avoided.
|
||||||
|
//
|
||||||
|
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
|
||||||
|
// testing.
|
||||||
|
//
|
||||||
|
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
|
||||||
|
app_state::queue_gl_or_metal_redraw(self.window);
|
||||||
|
} else {
|
||||||
|
let () = msg_send![self.view, setNeedsDisplay];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,8 +79,8 @@ impl Inner {
|
||||||
unsafe {
|
unsafe {
|
||||||
let safe_area = self.safe_area_screen_space();
|
let safe_area = self.safe_area_screen_space();
|
||||||
Ok(LogicalPosition {
|
Ok(LogicalPosition {
|
||||||
x: safe_area.origin.x,
|
x: safe_area.origin.x as _,
|
||||||
y: safe_area.origin.y,
|
y: safe_area.origin.y as _,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,8 +89,8 @@ impl Inner {
|
||||||
unsafe {
|
unsafe {
|
||||||
let screen_frame = self.screen_frame();
|
let screen_frame = self.screen_frame();
|
||||||
Ok(LogicalPosition {
|
Ok(LogicalPosition {
|
||||||
x: screen_frame.origin.x,
|
x: screen_frame.origin.x as _,
|
||||||
y: screen_frame.origin.y,
|
y: screen_frame.origin.y as _,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,8 +114,8 @@ impl Inner {
|
||||||
unsafe {
|
unsafe {
|
||||||
let safe_area = self.safe_area_screen_space();
|
let safe_area = self.safe_area_screen_space();
|
||||||
LogicalSize {
|
LogicalSize {
|
||||||
width: safe_area.size.width,
|
width: safe_area.size.width as _,
|
||||||
height: safe_area.size.height,
|
height: safe_area.size.height as _,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,8 +124,8 @@ impl Inner {
|
||||||
unsafe {
|
unsafe {
|
||||||
let screen_frame = self.screen_frame();
|
let screen_frame = self.screen_frame();
|
||||||
LogicalSize {
|
LogicalSize {
|
||||||
width: screen_frame.size.width,
|
width: screen_frame.size.width as _,
|
||||||
height: screen_frame.size.height,
|
height: screen_frame.size.height as _,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,26 +173,41 @@ impl Inner {
|
||||||
warn!("`Window::set_maximized` is ignored on iOS")
|
warn!("`Window::set_maximized` is ignored on iOS")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
match monitor {
|
let uiscreen = match monitor {
|
||||||
Some(monitor) => {
|
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||||
let uiscreen = monitor.ui_screen() as id;
|
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
|
||||||
let current: id = msg_send![self.window, screen];
|
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
uiscreen
|
||||||
|
|
||||||
// this is pretty slow on iOS, so avoid doing it if we can
|
|
||||||
if uiscreen != current {
|
|
||||||
let () = msg_send![self.window, setScreen: uiscreen];
|
|
||||||
}
|
|
||||||
let () = msg_send![self.window, setFrame: bounds];
|
|
||||||
}
|
}
|
||||||
None => warn!("`Window::set_fullscreen(None)` ignored on iOS"),
|
Some(Fullscreen::Borderless(monitor)) => monitor.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: 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
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let monitor = self.current_monitor();
|
let monitor = self.current_monitor();
|
||||||
let uiscreen = monitor.inner.ui_screen();
|
let uiscreen = monitor.inner.ui_screen();
|
||||||
|
@ -189,21 +220,15 @@ impl Inner {
|
||||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||||
{
|
{
|
||||||
Some(monitor)
|
Some(Fullscreen::Borderless(monitor))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_decorations(&self, decorations: bool) {
|
pub fn set_decorations(&self, _decorations: bool) {
|
||||||
unsafe {
|
warn!("`Window::set_decorations` is ignored on iOS")
|
||||||
let status_bar_hidden = if decorations { NO } else { YES };
|
|
||||||
let () = msg_send![
|
|
||||||
self.view_controller,
|
|
||||||
setPrefersStatusBarHidden: status_bar_hidden
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||||
|
@ -238,6 +263,16 @@ impl Inner {
|
||||||
pub fn id(&self) -> WindowId {
|
pub fn id(&self) -> WindowId {
|
||||||
self.window.into()
|
self.window.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
let handle = IOSHandle {
|
||||||
|
ui_window: self.window as _,
|
||||||
|
ui_view: self.view as _,
|
||||||
|
ui_view_controller: self.view_controller as _,
|
||||||
|
..IOSHandle::empty()
|
||||||
|
};
|
||||||
|
RawWindowHandle::IOS(handle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
|
@ -277,7 +312,7 @@ impl DerefMut for Window {
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn new<T>(
|
pub fn new<T>(
|
||||||
event_loop: &EventLoopWindowTarget<T>,
|
_event_loop: &EventLoopWindowTarget<T>,
|
||||||
window_attributes: WindowAttributes,
|
window_attributes: WindowAttributes,
|
||||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||||
) -> Result<Window, RootOsError> {
|
) -> Result<Window, RootOsError> {
|
||||||
|
@ -293,25 +328,38 @@ impl Window {
|
||||||
// TODO: transparency, visible
|
// TODO: transparency, visible
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let screen = window_attributes
|
let screen = match window_attributes.fullscreen {
|
||||||
.fullscreen
|
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||||
.as_ref()
|
video_mode.video_mode.monitor.ui_screen() as id
|
||||||
.map(|screen| screen.ui_screen() as _)
|
}
|
||||||
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen());
|
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
|
||||||
|
None => monitor::main_uiscreen().ui_screen(),
|
||||||
|
};
|
||||||
|
|
||||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
let screen_bounds: CGRect = msg_send![screen, bounds];
|
||||||
|
|
||||||
let frame = match window_attributes.inner_size {
|
let frame = match window_attributes.inner_size {
|
||||||
Some(dim) => CGRect {
|
Some(dim) => CGRect {
|
||||||
origin: screen_bounds.origin,
|
origin: screen_bounds.origin,
|
||||||
size: CGSize {
|
size: CGSize {
|
||||||
width: dim.width,
|
width: dim.width as _,
|
||||||
height: dim.height,
|
height: dim.height as _,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
None => screen_bounds,
|
None => screen_bounds,
|
||||||
};
|
};
|
||||||
|
|
||||||
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
|
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
|
||||||
|
|
||||||
|
let gl_or_metal_backed = {
|
||||||
|
let view_class: id = msg_send![view, class];
|
||||||
|
let layer_class: id = 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 =
|
let view_controller =
|
||||||
view::create_view_controller(&window_attributes, &platform_attributes, view);
|
view::create_view_controller(&window_attributes, &platform_attributes, view);
|
||||||
let window = view::create_window(
|
let window = view::create_window(
|
||||||
|
@ -321,17 +369,41 @@ impl Window {
|
||||||
view_controller,
|
view_controller,
|
||||||
);
|
);
|
||||||
|
|
||||||
let supports_safe_area = event_loop.capabilities().supports_safe_area;
|
|
||||||
|
|
||||||
let result = Window {
|
let result = Window {
|
||||||
inner: Inner {
|
inner: Inner {
|
||||||
window,
|
window,
|
||||||
view_controller,
|
view_controller,
|
||||||
view,
|
view,
|
||||||
supports_safe_area,
|
gl_or_metal_backed,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
AppState::set_key_window(window);
|
app_state::set_key_window(window);
|
||||||
|
|
||||||
|
// Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized`
|
||||||
|
// event on window creation if the DPI factor != 1.0
|
||||||
|
let hidpi_factor: CGFloat = msg_send![view, contentScaleFactor];
|
||||||
|
if hidpi_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(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(window.into()),
|
||||||
|
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
|
||||||
|
})
|
||||||
|
.chain(std::iter::once(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(window.into()),
|
||||||
|
event: WindowEvent::Resized(size),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,6 +442,36 @@ impl Inner {
|
||||||
msg_send![
|
msg_send![
|
||||||
self.view_controller,
|
self.view_controller,
|
||||||
setSupportedInterfaceOrientations: supported_orientations
|
setSupportedInterfaceOrientations: supported_orientations
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||||
|
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();
|
||||||
|
unsafe {
|
||||||
|
let () = msg_send![
|
||||||
|
self.view_controller,
|
||||||
|
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||||
|
unsafe {
|
||||||
|
let status_bar_hidden = if hidden { YES } else { NO };
|
||||||
|
let () = msg_send![
|
||||||
|
self.view_controller,
|
||||||
|
setPrefersStatusBarHidden: status_bar_hidden
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,7 +508,7 @@ impl Inner {
|
||||||
// requires main thread
|
// requires main thread
|
||||||
unsafe fn safe_area_screen_space(&self) -> CGRect {
|
unsafe fn safe_area_screen_space(&self) -> CGRect {
|
||||||
let bounds: CGRect = msg_send![self.window, bounds];
|
let bounds: CGRect = msg_send![self.window, bounds];
|
||||||
if self.supports_safe_area {
|
if app_state::os_capabilities().safe_area {
|
||||||
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
|
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
|
||||||
let safe_bounds = CGRect {
|
let safe_bounds = CGRect {
|
||||||
origin: CGPoint {
|
origin: CGPoint {
|
||||||
|
@ -494,6 +596,9 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub root_view_class: &'static Class,
|
pub root_view_class: &'static Class,
|
||||||
pub hidpi_factor: Option<f64>,
|
pub hidpi_factor: Option<f64>,
|
||||||
pub valid_orientations: ValidOrientations,
|
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 {
|
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
|
@ -502,6 +607,9 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
root_view_class: class!(UIView),
|
root_view_class: class!(UIView),
|
||||||
hidpi_factor: None,
|
hidpi_factor: None,
|
||||||
valid_orientations: Default::default(),
|
valid_orientations: Default::default(),
|
||||||
|
prefers_home_indicator_hidden: false,
|
||||||
|
prefers_status_bar_hidden: false,
|
||||||
|
preferred_screen_edges_deferring_system_gestures: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
|
||||||
|
|
||||||
pub const RTLD_LAZY: c_int = 0x001;
|
|
||||||
pub const RTLD_NOW: c_int = 0x002;
|
|
||||||
|
|
||||||
#[link(name = "dl")]
|
|
||||||
extern "C" {
|
|
||||||
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
|
|
||||||
pub fn dlerror() -> *mut c_char;
|
|
||||||
pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
|
|
||||||
pub fn dlclose(handle: *mut c_void) -> c_int;
|
|
||||||
}
|
|
|
@ -1,23 +1,25 @@
|
||||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||||
|
|
||||||
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem, os::raw::*, sync::Arc};
|
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc};
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use raw_window_handle::RawWindowHandle;
|
||||||
use smithay_client_toolkit::reexports::client::ConnectError;
|
use smithay_client_toolkit::reexports::client::ConnectError;
|
||||||
|
|
||||||
pub use self::x11::XNotSupported;
|
pub use self::x11::XNotSupported;
|
||||||
use self::x11::{ffi::XVisualInfo, XConnection, XError};
|
use self::x11::{
|
||||||
|
ffi::XVisualInfo, get_xtarget, util::WindowType as XWindowType, XConnection, XError,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
event::Event,
|
event::Event,
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||||
icon::Icon,
|
icon::Icon,
|
||||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode},
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod dlopen;
|
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
pub mod x11;
|
pub mod x11;
|
||||||
|
|
||||||
|
@ -30,7 +32,7 @@ pub mod x11;
|
||||||
/// If this variable is set with any other value, winit will panic.
|
/// If this variable is set with any other value, winit will panic.
|
||||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone)]
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub visual_infos: Option<XVisualInfo>,
|
pub visual_infos: Option<XVisualInfo>,
|
||||||
pub screen_id: Option<i32>,
|
pub screen_id: Option<i32>,
|
||||||
|
@ -38,11 +40,27 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub base_size: Option<(u32, u32)>,
|
pub base_size: Option<(u32, u32)>,
|
||||||
pub class: Option<(String, String)>,
|
pub class: Option<(String, String)>,
|
||||||
pub override_redirect: bool,
|
pub override_redirect: bool,
|
||||||
pub x11_window_type: x11::util::WindowType,
|
pub x11_window_types: Vec<XWindowType>,
|
||||||
pub gtk_theme_variant: Option<String>,
|
pub gtk_theme_variant: Option<String>,
|
||||||
pub app_id: Option<String>,
|
pub app_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
visual_infos: None,
|
||||||
|
screen_id: None,
|
||||||
|
resize_increments: None,
|
||||||
|
base_size: None,
|
||||||
|
class: None,
|
||||||
|
override_redirect: false,
|
||||||
|
x11_window_types: vec![XWindowType::Normal],
|
||||||
|
gtk_theme_variant: None,
|
||||||
|
app_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
|
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
|
||||||
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
|
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
|
||||||
|
@ -92,7 +110,7 @@ impl DeviceId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum MonitorHandle {
|
pub enum MonitorHandle {
|
||||||
X(x11::MonitorHandle),
|
X(x11::MonitorHandle),
|
||||||
Wayland(wayland::MonitorHandle),
|
Wayland(wayland::MonitorHandle),
|
||||||
|
@ -140,7 +158,7 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
|
||||||
match self {
|
match self {
|
||||||
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
||||||
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
||||||
|
@ -148,6 +166,46 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum VideoMode {
|
||||||
|
X(x11::VideoMode),
|
||||||
|
Wayland(wayland::VideoMode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.size(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.bit_depth(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.bit_depth(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.refresh_rate(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.refresh_rate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.monitor(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.monitor(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new<T>(
|
pub fn new<T>(
|
||||||
|
@ -310,17 +368,15 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
match self {
|
match self {
|
||||||
&Window::X(ref w) => w.fullscreen(),
|
&Window::X(ref w) => w.fullscreen(),
|
||||||
&Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle {
|
&Window::Wayland(ref w) => w.fullscreen(),
|
||||||
inner: MonitorHandle::Wayland(monitor_id),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||||
match self {
|
match self {
|
||||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
&Window::X(ref w) => w.set_fullscreen(monitor),
|
||||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
||||||
|
@ -402,6 +458,13 @@ impl Window {
|
||||||
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
|
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
match self {
|
||||||
|
&Window::X(ref window) => RawWindowHandle::X11(window.raw_window_handle()),
|
||||||
|
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn x_error_callback(
|
unsafe extern "C" fn x_error_callback(
|
||||||
|
@ -410,14 +473,16 @@ unsafe extern "C" fn x_error_callback(
|
||||||
) -> c_int {
|
) -> c_int {
|
||||||
let xconn_lock = X11_BACKEND.lock();
|
let xconn_lock = X11_BACKEND.lock();
|
||||||
if let Ok(ref xconn) = *xconn_lock {
|
if let Ok(ref xconn) = *xconn_lock {
|
||||||
let mut buf: [c_char; 1024] = mem::uninitialized();
|
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||||
|
// which do not require initialization.
|
||||||
|
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
|
||||||
(xconn.xlib.XGetErrorText)(
|
(xconn.xlib.XGetErrorText)(
|
||||||
display,
|
display,
|
||||||
(*event).error_code as c_int,
|
(*event).error_code as c_int,
|
||||||
buf.as_mut_ptr(),
|
buf.as_mut_ptr() as *mut c_char,
|
||||||
buf.len() as c_int,
|
buf.len() as c_int,
|
||||||
);
|
);
|
||||||
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
|
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
|
||||||
|
|
||||||
let error = XError {
|
let error = XError {
|
||||||
description: description.into_owned(),
|
description: description.into_owned(),
|
||||||
|
@ -439,12 +504,20 @@ pub enum EventLoop<T: 'static> {
|
||||||
X(x11::EventLoop<T>),
|
X(x11::EventLoop<T>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum EventLoopProxy<T: 'static> {
|
pub enum EventLoopProxy<T: 'static> {
|
||||||
X(x11::EventLoopProxy<T>),
|
X(x11::EventLoopProxy<T>),
|
||||||
Wayland(wayland::EventLoopProxy<T>),
|
Wayland(wayland::EventLoopProxy<T>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()),
|
||||||
|
EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
impl<T: 'static> EventLoop<T> {
|
||||||
pub fn new() -> EventLoop<T> {
|
pub fn new() -> EventLoop<T> {
|
||||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||||
|
@ -502,7 +575,7 @@ impl<T: 'static> EventLoop<T> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(MonitorHandle::Wayland)
|
.map(MonitorHandle::Wayland)
|
||||||
.collect(),
|
.collect(),
|
||||||
EventLoop::X(ref evlp) => evlp
|
EventLoop::X(ref evlp) => get_xtarget(&evlp.target)
|
||||||
.x_connection()
|
.x_connection()
|
||||||
.available_monitors()
|
.available_monitors()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -515,7 +588,9 @@ impl<T: 'static> EventLoop<T> {
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||||
match *self {
|
match *self {
|
||||||
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
|
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
|
||||||
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()),
|
EventLoop::X(ref evlp) => {
|
||||||
|
MonitorHandle::X(get_xtarget(&evlp.target).x_connection().primary_monitor())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,14 +621,6 @@ impl<T: 'static> EventLoop<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_wayland(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
EventLoop::Wayland(_) => true,
|
|
||||||
EventLoop::X(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
||||||
match *self {
|
match *self {
|
||||||
EventLoop::Wayland(ref evl) => evl.window_target(),
|
EventLoop::Wayland(ref evl) => evl.window_target(),
|
||||||
|
@ -576,6 +643,16 @@ pub enum EventLoopWindowTarget<T> {
|
||||||
X(x11::EventLoopWindowTarget<T>),
|
X(x11::EventLoopWindowTarget<T>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> EventLoopWindowTarget<T> {
|
||||||
|
#[inline]
|
||||||
|
pub fn is_wayland(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
EventLoopWindowTarget::Wayland(_) => true,
|
||||||
|
EventLoopWindowTarget::X(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sticky_exit_callback<T, F>(
|
fn sticky_exit_callback<T, F>(
|
||||||
evt: Event<T>,
|
evt: Event<T>,
|
||||||
target: &RootELW<T>,
|
target: &RootELW<T>,
|
||||||
|
|
|
@ -16,8 +16,11 @@ use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
event::ModifiersState,
|
event::ModifiersState,
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
platform_impl::platform::sticky_exit_callback,
|
platform_impl::platform::{
|
||||||
|
sticky_exit_callback, MonitorHandle as PlatformMonitorHandle,
|
||||||
|
VideoMode as PlatformVideoMode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{window::WindowStore, DeviceId, WindowId};
|
use super::{window::WindowStore, DeviceId, WindowId};
|
||||||
|
@ -87,7 +90,6 @@ pub struct EventLoop<T: 'static> {
|
||||||
// A handle that can be sent across threads and used to wake up the `EventLoop`.
|
// A handle that can be sent across threads and used to wake up the `EventLoop`.
|
||||||
//
|
//
|
||||||
// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs.
|
// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
pub struct EventLoopProxy<T: 'static> {
|
||||||
user_sender: ::calloop::channel::Sender<T>,
|
user_sender: ::calloop::channel::Sender<T>,
|
||||||
}
|
}
|
||||||
|
@ -108,6 +110,14 @@ pub struct EventLoopWindowTarget<T> {
|
||||||
_marker: ::std::marker::PhantomData<T>,
|
_marker: ::std::marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
EventLoopProxy {
|
||||||
|
user_sender: self.user_sender.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
impl<T: 'static> EventLoopProxy<T> {
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
||||||
self.user_sender.send(event).map_err(|_| EventLoopClosed)
|
self.user_sender.send(event).map_err(|_| EventLoopClosed)
|
||||||
|
@ -138,7 +148,7 @@ impl<T: 'static> EventLoop<T> {
|
||||||
|
|
||||||
let mut seat_manager = SeatManager {
|
let mut seat_manager = SeatManager {
|
||||||
sink: sink.clone(),
|
sink: sink.clone(),
|
||||||
relative_pointer_manager_proxy: None,
|
relative_pointer_manager_proxy: Rc::new(RefCell::new(None)),
|
||||||
store: store.clone(),
|
store: store.clone(),
|
||||||
seats: seats.clone(),
|
seats: seats.clone(),
|
||||||
kbd_sender,
|
kbd_sender,
|
||||||
|
@ -154,13 +164,16 @@ impl<T: 'static> EventLoop<T> {
|
||||||
version,
|
version,
|
||||||
} => {
|
} => {
|
||||||
if interface == "zwp_relative_pointer_manager_v1" {
|
if interface == "zwp_relative_pointer_manager_v1" {
|
||||||
seat_manager.relative_pointer_manager_proxy = Some(
|
let relative_pointer_manager_proxy = registry
|
||||||
registry
|
.bind(version, id, move |pointer_manager| {
|
||||||
.bind(version, id, move |pointer_manager| {
|
pointer_manager.implement_closure(|_, _| (), ())
|
||||||
pointer_manager.implement_closure(|_, _| (), ())
|
})
|
||||||
})
|
.unwrap();
|
||||||
.unwrap(),
|
|
||||||
)
|
*seat_manager
|
||||||
|
.relative_pointer_manager_proxy
|
||||||
|
.try_borrow_mut()
|
||||||
|
.unwrap() = Some(relative_pointer_manager_proxy);
|
||||||
}
|
}
|
||||||
if interface == "wl_seat" {
|
if interface == "wl_seat" {
|
||||||
seat_manager.add_seat(id, version, registry)
|
seat_manager.add_seat(id, version, registry)
|
||||||
|
@ -304,6 +317,25 @@ impl<T: 'static> EventLoop<T> {
|
||||||
// send pending events to the server
|
// send pending events to the server
|
||||||
self.display.flush().expect("Wayland connection lost.");
|
self.display.flush().expect("Wayland connection lost.");
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// is the case, some events may have been enqueued in our event queue.
|
||||||
|
//
|
||||||
|
// If some messages are there, the event loop needs to behave as if it was instantly
|
||||||
|
// woken up by messages arriving from the wayland socket, to avoid getting stuck.
|
||||||
|
let instant_wakeup = {
|
||||||
|
let window_target = match self.window_target.p {
|
||||||
|
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let dispatched = window_target
|
||||||
|
.evq
|
||||||
|
.borrow_mut()
|
||||||
|
.dispatch_pending()
|
||||||
|
.expect("Wayland connection lost.");
|
||||||
|
dispatched > 0
|
||||||
|
};
|
||||||
|
|
||||||
match control_flow {
|
match control_flow {
|
||||||
ControlFlow::Exit => break,
|
ControlFlow::Exit => break,
|
||||||
ControlFlow::Poll => {
|
ControlFlow::Poll => {
|
||||||
|
@ -318,7 +350,12 @@ impl<T: 'static> EventLoop<T> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ControlFlow::Wait => {
|
ControlFlow::Wait => {
|
||||||
self.inner_loop.dispatch(None, &mut ()).unwrap();
|
let timeout = if instant_wakeup {
|
||||||
|
Some(::std::time::Duration::from_millis(0))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.inner_loop.dispatch(timeout, &mut ()).unwrap();
|
||||||
callback(
|
callback(
|
||||||
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
|
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
|
||||||
start: Instant::now(),
|
start: Instant::now(),
|
||||||
|
@ -331,7 +368,7 @@ impl<T: 'static> EventLoop<T> {
|
||||||
ControlFlow::WaitUntil(deadline) => {
|
ControlFlow::WaitUntil(deadline) => {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
// compute the blocking duration
|
// compute the blocking duration
|
||||||
let duration = if deadline > start {
|
let duration = if deadline > start && !instant_wakeup {
|
||||||
deadline - start
|
deadline - start
|
||||||
} else {
|
} else {
|
||||||
::std::time::Duration::from_millis(0)
|
::std::time::Duration::from_millis(0)
|
||||||
|
@ -380,15 +417,17 @@ impl<T: 'static> EventLoop<T> {
|
||||||
available_monitors(&self.outputs)
|
available_monitors(&self.outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display(&self) -> &Display {
|
|
||||||
&*self.display
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &RootELW<T> {
|
pub fn window_target(&self) -> &RootELW<T> {
|
||||||
&self.window_target
|
&self.window_target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> EventLoopWindowTarget<T> {
|
||||||
|
pub fn display(&self) -> &Display {
|
||||||
|
&*self.display
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Private EventLoop Internals
|
* Private EventLoop Internals
|
||||||
*/
|
*/
|
||||||
|
@ -457,7 +496,7 @@ struct SeatManager<T: 'static> {
|
||||||
store: Arc<Mutex<WindowStore>>,
|
store: Arc<Mutex<WindowStore>>,
|
||||||
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
|
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
|
||||||
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
||||||
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
|
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> SeatManager<T> {
|
impl<T: 'static> SeatManager<T> {
|
||||||
|
@ -469,7 +508,7 @@ impl<T: 'static> SeatManager<T> {
|
||||||
store: self.store.clone(),
|
store: self.store.clone(),
|
||||||
pointer: None,
|
pointer: None,
|
||||||
relative_pointer: None,
|
relative_pointer: None,
|
||||||
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.as_ref().cloned(),
|
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(),
|
||||||
keyboard: None,
|
keyboard: None,
|
||||||
touch: None,
|
touch: None,
|
||||||
kbd_sender: self.kbd_sender.clone(),
|
kbd_sender: self.kbd_sender.clone(),
|
||||||
|
@ -501,7 +540,7 @@ struct SeatData<T> {
|
||||||
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
||||||
pointer: Option<wl_pointer::WlPointer>,
|
pointer: Option<wl_pointer::WlPointer>,
|
||||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||||
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
|
relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
|
||||||
keyboard: Option<wl_keyboard::WlKeyboard>,
|
keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||||
touch: Option<wl_touch::WlTouch>,
|
touch: Option<wl_touch::WlTouch>,
|
||||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||||
|
@ -521,17 +560,19 @@ impl<T: 'static> SeatData<T> {
|
||||||
self.modifiers_tracker.clone(),
|
self.modifiers_tracker.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
self.relative_pointer =
|
self.relative_pointer = self
|
||||||
self.relative_pointer_manager_proxy
|
.relative_pointer_manager_proxy
|
||||||
.as_ref()
|
.try_borrow()
|
||||||
.and_then(|manager| {
|
.unwrap()
|
||||||
super::pointer::implement_relative_pointer(
|
.as_ref()
|
||||||
self.sink.clone(),
|
.and_then(|manager| {
|
||||||
self.pointer.as_ref().unwrap(),
|
super::pointer::implement_relative_pointer(
|
||||||
manager,
|
self.sink.clone(),
|
||||||
)
|
self.pointer.as_ref().unwrap(),
|
||||||
.ok()
|
manager,
|
||||||
})
|
)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// destroy pointer if applicable
|
// destroy pointer if applicable
|
||||||
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
||||||
|
@ -603,17 +644,67 @@ impl<T> Drop for SeatData<T> {
|
||||||
* Monitor stuff
|
* Monitor stuff
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct VideoMode {
|
||||||
|
pub(crate) size: (u32, u32),
|
||||||
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) monitor: MonitorHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
self.bit_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
self.refresh_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
pub(crate) proxy: wl_output::WlOutput,
|
pub(crate) proxy: wl_output::WlOutput,
|
||||||
pub(crate) mgr: OutputMgr,
|
pub(crate) mgr: OutputMgr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for MonitorHandle {
|
impl PartialEq for MonitorHandle {
|
||||||
fn clone(&self) -> MonitorHandle {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
MonitorHandle {
|
self.native_identifier() == other.native_identifier()
|
||||||
proxy: self.proxy.clone(),
|
}
|
||||||
mgr: self.mgr.clone(),
|
}
|
||||||
}
|
|
||||||
|
impl Eq for MonitorHandle {}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
self.native_identifier().cmp(&other.native_identifier())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for MonitorHandle {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.native_identifier().hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,15 +771,20 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
|
let monitor = self.clone();
|
||||||
|
|
||||||
self.mgr
|
self.mgr
|
||||||
.with_info(&self.proxy, |_, info| info.modes.clone())
|
.with_info(&self.proxy, |_, info| info.modes.clone())
|
||||||
.unwrap_or(vec![])
|
.unwrap_or(vec![])
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| VideoMode {
|
.map(move |x| RootVideoMode {
|
||||||
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
|
video_mode: PlatformVideoMode::Wayland(VideoMode {
|
||||||
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
|
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
|
||||||
bit_depth: 32,
|
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
|
||||||
|
bit_depth: 32,
|
||||||
|
monitor: monitor.clone(),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,17 @@ pub fn init_keyboard(
|
||||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||||
KbEvent::Modifiers {
|
KbEvent::Modifiers {
|
||||||
modifiers: event_modifiers,
|
modifiers: event_modifiers,
|
||||||
} => *modifiers_tracker.lock().unwrap() = event_modifiers.into(),
|
} => {
|
||||||
|
let modifiers = event_modifiers.into();
|
||||||
|
|
||||||
|
*modifiers_tracker.lock().unwrap() = modifiers;
|
||||||
|
|
||||||
|
if let Some(wid) = *target.lock().unwrap() {
|
||||||
|
my_sink
|
||||||
|
.send((WindowEvent::ModifiersChanged { modifiers }, wid))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move |repeat_event: KeyRepeatEvent, _| {
|
move |repeat_event: KeyRepeatEvent, _| {
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{
|
event_loop::{
|
||||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink,
|
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
|
||||||
|
WindowEventsSink,
|
||||||
},
|
},
|
||||||
window::Window,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,6 +39,7 @@ pub(crate) fn implement_touch<T: 'static>(
|
||||||
),
|
),
|
||||||
phase: TouchPhase::Started,
|
phase: TouchPhase::Started,
|
||||||
location: (x, y).into(),
|
location: (x, y).into(),
|
||||||
|
force: None, // TODO
|
||||||
id: id as u64,
|
id: id as u64,
|
||||||
}),
|
}),
|
||||||
wid,
|
wid,
|
||||||
|
@ -61,6 +62,7 @@ pub(crate) fn implement_touch<T: 'static>(
|
||||||
),
|
),
|
||||||
phase: TouchPhase::Ended,
|
phase: TouchPhase::Ended,
|
||||||
location: pt.location.into(),
|
location: pt.location.into(),
|
||||||
|
force: None, // TODO
|
||||||
id: id as u64,
|
id: id as u64,
|
||||||
}),
|
}),
|
||||||
pt.wid,
|
pt.wid,
|
||||||
|
@ -78,6 +80,7 @@ pub(crate) fn implement_touch<T: 'static>(
|
||||||
),
|
),
|
||||||
phase: TouchPhase::Moved,
|
phase: TouchPhase::Moved,
|
||||||
location: (x, y).into(),
|
location: (x, y).into(),
|
||||||
|
force: None, // TODO
|
||||||
id: id as u64,
|
id: id as u64,
|
||||||
}),
|
}),
|
||||||
pt.wid,
|
pt.wid,
|
||||||
|
@ -94,6 +97,7 @@ pub(crate) fn implement_touch<T: 'static>(
|
||||||
),
|
),
|
||||||
phase: TouchPhase::Cancelled,
|
phase: TouchPhase::Cancelled,
|
||||||
location: pt.location.into(),
|
location: pt.location.into(),
|
||||||
|
force: None, // TODO
|
||||||
id: pt.id as u64,
|
id: pt.id as u64,
|
||||||
}),
|
}),
|
||||||
pt.wid,
|
pt.wid,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use raw_window_handle::unix::WaylandHandle;
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
sync::{Arc, Mutex, Weak},
|
sync::{Arc, Mutex, Weak},
|
||||||
|
@ -8,10 +9,11 @@ use crate::{
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
monitor::MonitorHandle as RootMonitorHandle,
|
||||||
platform_impl::{
|
platform_impl::{
|
||||||
|
platform::wayland::event_loop::{available_monitors, primary_monitor},
|
||||||
MonitorHandle as PlatformMonitorHandle,
|
MonitorHandle as PlatformMonitorHandle,
|
||||||
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use smithay_client_toolkit::{
|
use smithay_client_toolkit::{
|
||||||
|
@ -25,7 +27,6 @@ use smithay_client_toolkit::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
||||||
use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
|
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
surface: wl_surface::WlSurface,
|
surface: wl_surface::WlSurface,
|
||||||
|
@ -108,13 +109,19 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for fullscreen requirements
|
// Check for fullscreen requirements
|
||||||
if let Some(RootMonitorHandle {
|
match attributes.fullscreen {
|
||||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
Some(Fullscreen::Exclusive(_)) => {
|
||||||
}) = attributes.fullscreen
|
panic!("Wayland doesn't support exclusive fullscreen")
|
||||||
{
|
}
|
||||||
frame.set_fullscreen(Some(&monitor_id.proxy));
|
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||||
} else if attributes.maximized {
|
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||||
frame.set_maximized();
|
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
|
||||||
|
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||||
|
None => {
|
||||||
|
if attributes.maximized {
|
||||||
|
frame.set_maximized();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.set_resizable(attributes.resizable);
|
frame.set_resizable(attributes.resizable);
|
||||||
|
@ -252,25 +259,31 @@ impl Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fullscreen(&self) -> Option<MonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
if *(self.fullscreen.lock().unwrap()) {
|
if *(self.fullscreen.lock().unwrap()) {
|
||||||
Some(self.current_monitor())
|
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
if let Some(RootMonitorHandle {
|
match fullscreen {
|
||||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
Some(Fullscreen::Exclusive(_)) => {
|
||||||
}) = monitor
|
panic!("Wayland doesn't support exclusive fullscreen")
|
||||||
{
|
}
|
||||||
self.frame
|
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||||
.lock()
|
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||||
.unwrap()
|
})) => {
|
||||||
.set_fullscreen(Some(&monitor_id.proxy));
|
self.frame
|
||||||
} else {
|
.lock()
|
||||||
self.frame.lock().unwrap().unset_fullscreen();
|
.unwrap()
|
||||||
|
.set_fullscreen(Some(&monitor_id.proxy));
|
||||||
|
}
|
||||||
|
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||||
|
None => self.frame.lock().unwrap().unset_fullscreen(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,6 +334,14 @@ impl Window {
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||||
primary_monitor(&self.outputs)
|
primary_monitor(&self.outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raw_window_handle(&self) -> WaylandHandle {
|
||||||
|
WaylandHandle {
|
||||||
|
surface: self.surface().as_ref().c_ptr() as *mut _,
|
||||||
|
display: self.display().as_ref().c_ptr() as *mut _,
|
||||||
|
..WaylandHandle::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Window {
|
impl Drop for Window {
|
||||||
|
|
|
@ -8,6 +8,8 @@ use super::{
|
||||||
XExtension,
|
XExtension,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use util::modifiers::{ModifierKeyState, ModifierKeymap};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
|
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
|
||||||
|
@ -21,6 +23,9 @@ pub(super) struct EventProcessor<T: 'static> {
|
||||||
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
|
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
|
||||||
pub(super) xi2ext: XExtension,
|
pub(super) xi2ext: XExtension,
|
||||||
pub(super) target: Rc<RootELW<T>>,
|
pub(super) target: Rc<RootELW<T>>,
|
||||||
|
pub(super) mod_keymap: ModifierKeymap,
|
||||||
|
pub(super) device_mod_state: ModifierKeyState,
|
||||||
|
pub(super) window_mod_state: ModifierKeyState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventProcessor<T> {
|
impl<T: 'static> EventProcessor<T> {
|
||||||
|
@ -112,12 +117,22 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
let event_type = xev.get_type();
|
let event_type = xev.get_type();
|
||||||
match event_type {
|
match event_type {
|
||||||
ffi::MappingNotify => {
|
ffi::MappingNotify => {
|
||||||
unsafe {
|
let mapping: &ffi::XMappingEvent = xev.as_ref();
|
||||||
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
|
|
||||||
|
if mapping.request == ffi::MappingModifier
|
||||||
|
|| mapping.request == ffi::MappingKeyboard
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
|
||||||
|
}
|
||||||
|
wt.xconn
|
||||||
|
.check_errors()
|
||||||
|
.expect("Failed to call XRefreshKeyboardMapping");
|
||||||
|
|
||||||
|
self.mod_keymap.reset_from_x_connection(&wt.xconn);
|
||||||
|
self.device_mod_state.update(&self.mod_keymap);
|
||||||
|
self.window_mod_state.update(&self.mod_keymap);
|
||||||
}
|
}
|
||||||
wt.xconn
|
|
||||||
.check_errors()
|
|
||||||
.expect("Failed to call XRefreshKeyboardMapping");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ffi::ClientMessage => {
|
ffi::ClientMessage => {
|
||||||
|
@ -374,7 +389,11 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
|
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
|
||||||
monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
|
monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
|
||||||
let new_hidpi_factor = monitor.hidpi_factor;
|
let new_hidpi_factor = monitor.hidpi_factor;
|
||||||
shared_state_lock.last_monitor = Some(monitor.clone());
|
|
||||||
|
// Avoid caching an invalid dummy monitor handle
|
||||||
|
if monitor.id != 0 {
|
||||||
|
shared_state_lock.last_monitor = Some(monitor.clone());
|
||||||
|
}
|
||||||
new_hidpi_factor
|
new_hidpi_factor
|
||||||
};
|
};
|
||||||
if last_hidpi_factor != new_hidpi_factor {
|
if last_hidpi_factor != new_hidpi_factor {
|
||||||
|
@ -514,13 +533,6 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
|
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
|
||||||
// a keycode of 0.
|
// a keycode of 0.
|
||||||
if xkev.keycode != 0 {
|
if xkev.keycode != 0 {
|
||||||
let modifiers = ModifiersState {
|
|
||||||
alt: xkev.state & ffi::Mod1Mask != 0,
|
|
||||||
shift: xkev.state & ffi::ShiftMask != 0,
|
|
||||||
ctrl: xkev.state & ffi::ControlMask != 0,
|
|
||||||
logo: xkev.state & ffi::Mod4Mask != 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let keysym = unsafe {
|
let keysym = unsafe {
|
||||||
let mut keysym = 0;
|
let mut keysym = 0;
|
||||||
(wt.xconn.xlib.XLookupString)(
|
(wt.xconn.xlib.XLookupString)(
|
||||||
|
@ -535,6 +547,8 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
};
|
};
|
||||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
||||||
|
|
||||||
|
let modifiers = self.window_mod_state.modifiers();
|
||||||
|
|
||||||
callback(Event::WindowEvent {
|
callback(Event::WindowEvent {
|
||||||
window_id,
|
window_id,
|
||||||
event: WindowEvent::KeyboardInput {
|
event: WindowEvent::KeyboardInput {
|
||||||
|
@ -547,6 +561,27 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(modifier) =
|
||||||
|
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
|
||||||
|
{
|
||||||
|
self.window_mod_state.key_event(
|
||||||
|
state,
|
||||||
|
xkev.keycode as ffi::KeyCode,
|
||||||
|
modifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_modifiers = self.window_mod_state.modifiers();
|
||||||
|
|
||||||
|
if modifiers != new_modifiers {
|
||||||
|
callback(Event::WindowEvent {
|
||||||
|
window_id,
|
||||||
|
event: WindowEvent::ModifiersChanged {
|
||||||
|
modifiers: new_modifiers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state == Pressed {
|
if state == Pressed {
|
||||||
|
@ -859,6 +894,21 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
event: Focused(true),
|
event: Focused(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When focus is gained, send any existing modifiers
|
||||||
|
// to the window in a ModifiersChanged event. This is
|
||||||
|
// done to compensate for modifier keys that may be
|
||||||
|
// changed while a window is out of focus.
|
||||||
|
if !self.device_mod_state.is_empty() {
|
||||||
|
self.window_mod_state = self.device_mod_state.clone();
|
||||||
|
|
||||||
|
let modifiers = self.window_mod_state.modifiers();
|
||||||
|
|
||||||
|
callback(Event::WindowEvent {
|
||||||
|
window_id,
|
||||||
|
event: WindowEvent::ModifiersChanged { modifiers },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// The deviceid for this event is for a keyboard instead of a pointer,
|
// The deviceid for this event is for a keyboard instead of a pointer,
|
||||||
// so we have to do a little extra work.
|
// so we have to do a little extra work.
|
||||||
let pointer_id = self
|
let pointer_id = self
|
||||||
|
@ -890,6 +940,22 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.unfocus(xev.event)
|
.unfocus(xev.event)
|
||||||
.expect("Failed to unfocus input context");
|
.expect("Failed to unfocus input context");
|
||||||
|
|
||||||
|
// When focus is lost, send a ModifiersChanged event
|
||||||
|
// containing no modifiers set. This is done to compensate
|
||||||
|
// for modifier keys that may be changed while a window
|
||||||
|
// is out of focus.
|
||||||
|
if !self.window_mod_state.is_empty() {
|
||||||
|
self.window_mod_state.clear();
|
||||||
|
|
||||||
|
callback(Event::WindowEvent {
|
||||||
|
window_id: mkwid(xev.event),
|
||||||
|
event: WindowEvent::ModifiersChanged {
|
||||||
|
modifiers: ModifiersState::default(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
callback(Event::WindowEvent {
|
callback(Event::WindowEvent {
|
||||||
window_id: mkwid(xev.event),
|
window_id: mkwid(xev.event),
|
||||||
event: Focused(false),
|
event: Focused(false),
|
||||||
|
@ -918,6 +984,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
device_id: mkdid(xev.deviceid),
|
device_id: mkdid(xev.deviceid),
|
||||||
phase,
|
phase,
|
||||||
location,
|
location,
|
||||||
|
force: None, // TODO
|
||||||
id: xev.detail as u64,
|
id: xev.detail as u64,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
@ -1021,18 +1088,25 @@ impl<T: 'static> EventProcessor<T> {
|
||||||
|
|
||||||
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
||||||
|
|
||||||
|
if let Some(modifier) =
|
||||||
|
self.mod_keymap.get_modifier(keycode as ffi::KeyCode)
|
||||||
|
{
|
||||||
|
self.device_mod_state.key_event(
|
||||||
|
state,
|
||||||
|
keycode as ffi::KeyCode,
|
||||||
|
modifier,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let modifiers = self.device_mod_state.modifiers();
|
||||||
|
|
||||||
callback(Event::DeviceEvent {
|
callback(Event::DeviceEvent {
|
||||||
device_id: mkdid(device_id),
|
device_id: mkdid(device_id),
|
||||||
event: DeviceEvent::Key(KeyboardInput {
|
event: DeviceEvent::Key(KeyboardInput {
|
||||||
scancode,
|
scancode,
|
||||||
virtual_keycode,
|
virtual_keycode,
|
||||||
state,
|
state,
|
||||||
// So, in an ideal world we can use libxkbcommon to get modifiers.
|
modifiers,
|
||||||
// However, libxkbcommon-x11 isn't as commonly installed as one
|
|
||||||
// would hope. We can still use the Xkb extension to get
|
|
||||||
// comprehensive keyboard state updates, but interpreting that
|
|
||||||
// info manually is going to be involved.
|
|
||||||
modifiers: ModifiersState::default(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod window;
|
||||||
mod xdisplay;
|
mod xdisplay;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::UnownedWindow,
|
window::UnownedWindow,
|
||||||
xdisplay::{XConnection, XError, XNotSupported},
|
xdisplay::{XConnection, XError, XNotSupported},
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
ffi::CStr,
|
ffi::CStr,
|
||||||
mem,
|
mem::{self, MaybeUninit},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
os::raw::*,
|
os::raw::*,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -34,6 +34,7 @@ use self::{
|
||||||
dnd::{Dnd, DndState},
|
dnd::{Dnd, DndState},
|
||||||
event_processor::EventProcessor,
|
event_processor::EventProcessor,
|
||||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
|
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
|
||||||
|
util::modifiers::ModifierKeymap,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
error::OsError as RootOsError,
|
error::OsError as RootOsError,
|
||||||
|
@ -60,16 +61,24 @@ pub struct EventLoop<T: 'static> {
|
||||||
_x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>,
|
_x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>,
|
||||||
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
|
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
|
||||||
pending_user_events: Rc<RefCell<VecDeque<T>>>,
|
pending_user_events: Rc<RefCell<VecDeque<T>>>,
|
||||||
|
event_processor: Rc<RefCell<EventProcessor<T>>>,
|
||||||
user_sender: ::calloop::channel::Sender<T>,
|
user_sender: ::calloop::channel::Sender<T>,
|
||||||
pending_events: Rc<RefCell<VecDeque<Event<T>>>>,
|
pending_events: Rc<RefCell<VecDeque<Event<T>>>>,
|
||||||
target: Rc<RootELW<T>>,
|
pub(crate) target: Rc<RootELW<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
pub struct EventLoopProxy<T: 'static> {
|
||||||
user_sender: ::calloop::channel::Sender<T>,
|
user_sender: ::calloop::channel::Sender<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
EventLoopProxy {
|
||||||
|
user_sender: self.user_sender.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
impl<T: 'static> EventLoop<T> {
|
||||||
pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
|
pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
|
||||||
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
||||||
|
@ -100,22 +109,21 @@ impl<T: 'static> EventLoop<T> {
|
||||||
.expect("Failed to query XRandR extension");
|
.expect("Failed to query XRandR extension");
|
||||||
|
|
||||||
let xi2ext = unsafe {
|
let xi2ext = unsafe {
|
||||||
let mut result = XExtension {
|
let mut ext = XExtension::default();
|
||||||
opcode: mem::uninitialized(),
|
|
||||||
first_event_id: mem::uninitialized(),
|
|
||||||
first_error_id: mem::uninitialized(),
|
|
||||||
};
|
|
||||||
let res = (xconn.xlib.XQueryExtension)(
|
let res = (xconn.xlib.XQueryExtension)(
|
||||||
xconn.display,
|
xconn.display,
|
||||||
b"XInputExtension\0".as_ptr() as *const c_char,
|
b"XInputExtension\0".as_ptr() as *const c_char,
|
||||||
&mut result.opcode as *mut c_int,
|
&mut ext.opcode,
|
||||||
&mut result.first_event_id as *mut c_int,
|
&mut ext.first_event_id,
|
||||||
&mut result.first_error_id as *mut c_int,
|
&mut ext.first_error_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if res == ffi::False {
|
if res == ffi::False {
|
||||||
panic!("X server missing XInput extension");
|
panic!("X server missing XInput extension");
|
||||||
}
|
}
|
||||||
result
|
|
||||||
|
ext
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -136,6 +144,9 @@ impl<T: 'static> EventLoop<T> {
|
||||||
|
|
||||||
xconn.update_cached_wm_info(root);
|
xconn.update_cached_wm_info(root);
|
||||||
|
|
||||||
|
let mut mod_keymap = ModifierKeymap::new();
|
||||||
|
mod_keymap.reset_from_x_connection(&xconn);
|
||||||
|
|
||||||
let target = Rc::new(RootELW {
|
let target = Rc::new(RootELW {
|
||||||
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
||||||
ime,
|
ime,
|
||||||
|
@ -172,13 +183,16 @@ impl<T: 'static> EventLoop<T> {
|
||||||
// Handle X11 events
|
// Handle X11 events
|
||||||
let pending_events: Rc<RefCell<VecDeque<_>>> = Default::default();
|
let pending_events: Rc<RefCell<VecDeque<_>>> = Default::default();
|
||||||
|
|
||||||
let mut processor = EventProcessor {
|
let processor = EventProcessor {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
dnd,
|
dnd,
|
||||||
devices: Default::default(),
|
devices: Default::default(),
|
||||||
randr_event_offset,
|
randr_event_offset,
|
||||||
ime_receiver,
|
ime_receiver,
|
||||||
xi2ext,
|
xi2ext,
|
||||||
|
mod_keymap,
|
||||||
|
device_mod_state: Default::default(),
|
||||||
|
window_mod_state: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register for device hotplug events
|
// Register for device hotplug events
|
||||||
|
@ -190,6 +204,9 @@ impl<T: 'static> EventLoop<T> {
|
||||||
|
|
||||||
processor.init_device(ffi::XIAllDevices);
|
processor.init_device(ffi::XIAllDevices);
|
||||||
|
|
||||||
|
let processor = Rc::new(RefCell::new(processor));
|
||||||
|
let event_processor = processor.clone();
|
||||||
|
|
||||||
// Setup the X11 event source
|
// Setup the X11 event source
|
||||||
let mut x11_events =
|
let mut x11_events =
|
||||||
::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd);
|
::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd);
|
||||||
|
@ -198,16 +215,11 @@ impl<T: 'static> EventLoop<T> {
|
||||||
.handle()
|
.handle()
|
||||||
.insert_source(x11_events, {
|
.insert_source(x11_events, {
|
||||||
let pending_events = pending_events.clone();
|
let pending_events = pending_events.clone();
|
||||||
let mut callback = move |event| {
|
|
||||||
pending_events.borrow_mut().push_back(event);
|
|
||||||
};
|
|
||||||
move |evt, &mut ()| {
|
move |evt, &mut ()| {
|
||||||
if evt.readiness.is_readable() {
|
if evt.readiness.is_readable() {
|
||||||
// process all pending events
|
let mut processor = processor.borrow_mut();
|
||||||
let mut xev = unsafe { mem::uninitialized() };
|
let mut pending_events = pending_events.borrow_mut();
|
||||||
while unsafe { processor.poll_one_event(&mut xev) } {
|
drain_events(&mut processor, &mut pending_events);
|
||||||
processor.process_event(&mut xev, &mut callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -220,18 +232,13 @@ impl<T: 'static> EventLoop<T> {
|
||||||
_user_source,
|
_user_source,
|
||||||
user_sender,
|
user_sender,
|
||||||
pending_user_events,
|
pending_user_events,
|
||||||
|
event_processor,
|
||||||
target,
|
target,
|
||||||
};
|
};
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `XConnection` of this events loop.
|
|
||||||
#[inline]
|
|
||||||
pub fn x_connection(&self) -> &Arc<XConnection> {
|
|
||||||
&get_xtarget(&self.target).xconn
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||||
EventLoopProxy {
|
EventLoopProxy {
|
||||||
user_sender: self.user_sender.clone(),
|
user_sender: self.user_sender.clone(),
|
||||||
|
@ -249,6 +256,12 @@ impl<T: 'static> EventLoop<T> {
|
||||||
let mut control_flow = ControlFlow::default();
|
let mut control_flow = ControlFlow::default();
|
||||||
let wt = get_xtarget(&self.target);
|
let wt = get_xtarget(&self.target);
|
||||||
|
|
||||||
|
callback(
|
||||||
|
crate::event::Event::NewEvents(crate::event::StartCause::Init),
|
||||||
|
&self.target,
|
||||||
|
&mut control_flow,
|
||||||
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Empty the event buffer
|
// Empty the event buffer
|
||||||
{
|
{
|
||||||
|
@ -272,8 +285,10 @@ impl<T: 'static> EventLoop<T> {
|
||||||
}
|
}
|
||||||
// Empty the redraw requests
|
// Empty the redraw requests
|
||||||
{
|
{
|
||||||
let mut guard = wt.pending_redraws.lock().unwrap();
|
// Release the lock to prevent deadlock
|
||||||
for wid in guard.drain() {
|
let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect();
|
||||||
|
|
||||||
|
for wid in windows {
|
||||||
sticky_exit_callback(
|
sticky_exit_callback(
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
window_id: crate::window::WindowId(super::WindowId::X(wid)),
|
window_id: crate::window::WindowId(super::WindowId::X(wid)),
|
||||||
|
@ -354,6 +369,10 @@ impl<T: 'static> EventLoop<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user callback had any interaction with the X server,
|
||||||
|
// it may have received and buffered some user input events.
|
||||||
|
self.drain_events();
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(
|
callback(
|
||||||
|
@ -370,13 +389,43 @@ impl<T: 'static> EventLoop<T> {
|
||||||
self.run_return(callback);
|
self.run_return(callback);
|
||||||
::std::process::exit(0);
|
::std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drain_events(&self) {
|
||||||
|
let mut processor = self.event_processor.borrow_mut();
|
||||||
|
let mut pending_events = self.pending_events.borrow_mut();
|
||||||
|
|
||||||
|
drain_events(&mut processor, &mut pending_events);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_xtarget<T>(rt: &RootELW<T>) -> &EventLoopWindowTarget<T> {
|
fn drain_events<T: 'static>(
|
||||||
if let super::EventLoopWindowTarget::X(ref target) = rt.p {
|
processor: &mut EventProcessor<T>,
|
||||||
target
|
pending_events: &mut VecDeque<Event<T>>,
|
||||||
} else {
|
) {
|
||||||
unreachable!();
|
let mut callback = |event| {
|
||||||
|
pending_events.push_back(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// process all pending events
|
||||||
|
let mut xev = MaybeUninit::uninit();
|
||||||
|
while unsafe { processor.poll_one_event(xev.as_mut_ptr()) } {
|
||||||
|
let mut xev = unsafe { xev.assume_init() };
|
||||||
|
processor.process_event(&mut xev, &mut callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
|
||||||
|
match target.p {
|
||||||
|
super::EventLoopWindowTarget::X(ref target) => target,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> EventLoopWindowTarget<T> {
|
||||||
|
/// Returns the `XConnection` of this events loop.
|
||||||
|
#[inline]
|
||||||
|
pub fn x_connection(&self) -> &Arc<XConnection> {
|
||||||
|
&self.xconn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,19 +444,19 @@ struct DeviceInfo<'a> {
|
||||||
impl<'a> DeviceInfo<'a> {
|
impl<'a> DeviceInfo<'a> {
|
||||||
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
|
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut count = mem::uninitialized();
|
let mut count = 0;
|
||||||
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
|
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
|
||||||
xconn.check_errors().ok().and_then(|_| {
|
xconn.check_errors().ok()?;
|
||||||
if info.is_null() || count == 0 {
|
|
||||||
None
|
if info.is_null() || count == 0 {
|
||||||
} else {
|
None
|
||||||
Some(DeviceInfo {
|
} else {
|
||||||
xconn,
|
Some(DeviceInfo {
|
||||||
info,
|
xconn,
|
||||||
count: count as usize,
|
info,
|
||||||
})
|
count: count as usize,
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -512,7 +561,7 @@ impl<'a> Drop for GenericEventCookie<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
struct XExtension {
|
struct XExtension {
|
||||||
opcode: c_int,
|
opcode: c_int,
|
||||||
first_event_id: c_int,
|
first_event_id: c_int,
|
||||||
|
|
|
@ -4,47 +4,66 @@ use parking_lot::Mutex;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ffi::{
|
ffi::{
|
||||||
RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window,
|
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
|
||||||
XRRScreenResources,
|
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
|
||||||
},
|
},
|
||||||
util, XConnection, XError,
|
util, XConnection, XError,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used to test XRandR < 1.5 code path. This should always be committed as false.
|
// Used for testing. This should always be committed as false.
|
||||||
const FORCE_RANDR_COMPAT: bool = false;
|
|
||||||
// Also used for testing. This should always be committed as false.
|
|
||||||
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
|
|
||||||
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
|
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
|
|
||||||
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
|
|
||||||
if avail_major == major {
|
|
||||||
avail_minor >= minor
|
|
||||||
} else {
|
|
||||||
avail_major > major
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
|
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
|
||||||
// We update this lazily.
|
// We update this lazily.
|
||||||
(*MONITORS.lock()).take()
|
(*MONITORS.lock()).take()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct VideoMode {
|
||||||
|
pub(crate) size: (u32, u32),
|
||||||
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) native_mode: RRMode,
|
||||||
|
pub(crate) monitor: Option<MonitorHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
self.bit_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
self.refresh_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::X(self.monitor.clone().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
/// The actual id
|
/// The actual id
|
||||||
id: u32,
|
pub(crate) id: RRCrtc,
|
||||||
/// The name of the monitor
|
/// The name of the monitor
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
/// The size of the monitor
|
/// The size of the monitor
|
||||||
|
@ -61,16 +80,43 @@ pub struct MonitorHandle {
|
||||||
video_modes: Vec<VideoMode>,
|
video_modes: Vec<VideoMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for MonitorHandle {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for MonitorHandle {}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
self.id.cmp(&other.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for MonitorHandle {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MonitorHandle {
|
impl MonitorHandle {
|
||||||
fn from_repr(
|
fn new(
|
||||||
xconn: &XConnection,
|
xconn: &XConnection,
|
||||||
resources: *mut XRRScreenResources,
|
resources: *mut XRRScreenResources,
|
||||||
id: u32,
|
id: RRCrtc,
|
||||||
repr: util::MonitorRepr,
|
crtc: *mut XRRCrtcInfo,
|
||||||
primary: bool,
|
primary: bool,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? };
|
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
|
||||||
let (dimensions, position) = unsafe { (repr.size(), repr.position()) };
|
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
|
||||||
|
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
|
||||||
let rect = util::AaRect::new(position, dimensions);
|
let rect = util::AaRect::new(position, dimensions);
|
||||||
Some(MonitorHandle {
|
Some(MonitorHandle {
|
||||||
id,
|
id,
|
||||||
|
@ -84,6 +130,19 @@ impl MonitorHandle {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dummy() -> Self {
|
||||||
|
MonitorHandle {
|
||||||
|
id: 0,
|
||||||
|
name: "<dummy monitor>".into(),
|
||||||
|
hidpi_factor: 1.0,
|
||||||
|
dimensions: (1, 1),
|
||||||
|
position: (0, 0),
|
||||||
|
primary: true,
|
||||||
|
rect: util::AaRect::new((0, 0), (1, 1)),
|
||||||
|
video_modes: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> Option<String> {
|
pub fn name(&self) -> Option<String> {
|
||||||
Some(self.name.clone())
|
Some(self.name.clone())
|
||||||
}
|
}
|
||||||
|
@ -107,17 +166,27 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
self.video_modes.clone().into_iter()
|
let monitor = self.clone();
|
||||||
|
self.video_modes.clone().into_iter().map(move |mut x| {
|
||||||
|
x.monitor = Some(monitor.clone());
|
||||||
|
RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(x),
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XConnection {
|
impl XConnection {
|
||||||
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
|
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
|
||||||
let monitors = self.available_monitors();
|
let monitors = self.available_monitors();
|
||||||
let default = monitors
|
|
||||||
.get(0)
|
if monitors.is_empty() {
|
||||||
.expect("[winit] Failed to find any monitors using XRandR.");
|
// Return a dummy monitor to avoid panicking
|
||||||
|
return MonitorHandle::dummy();
|
||||||
|
}
|
||||||
|
|
||||||
|
let default = monitors.get(0).unwrap();
|
||||||
|
|
||||||
let window_rect = match window_rect {
|
let window_rect = match window_rect {
|
||||||
Some(rect) => rect,
|
Some(rect) => rect,
|
||||||
|
@ -139,8 +208,12 @@ impl XConnection {
|
||||||
|
|
||||||
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
|
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
let mut major = 0;
|
||||||
|
let mut minor = 0;
|
||||||
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
||||||
|
|
||||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||||
let resources = if version_is_at_least(1, 3) {
|
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
||||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||||
} else {
|
} else {
|
||||||
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
||||||
|
@ -155,48 +228,19 @@ impl XConnection {
|
||||||
let mut available;
|
let mut available;
|
||||||
let mut has_primary = false;
|
let mut has_primary = false;
|
||||||
|
|
||||||
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
|
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
||||||
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
|
available = Vec::with_capacity((*resources).ncrtc as usize);
|
||||||
// videowalls.
|
for crtc_index in 0..(*resources).ncrtc {
|
||||||
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
|
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
|
||||||
let mut monitor_count = 0;
|
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||||
let monitors =
|
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
|
||||||
(xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
|
if is_active {
|
||||||
assert!(monitor_count >= 0);
|
let is_primary = *(*crtc).outputs.offset(0) == primary;
|
||||||
available = Vec::with_capacity(monitor_count as usize);
|
|
||||||
for monitor_index in 0..monitor_count {
|
|
||||||
let monitor = monitors.offset(monitor_index as isize);
|
|
||||||
let is_primary = (*monitor).primary != 0;
|
|
||||||
has_primary |= is_primary;
|
has_primary |= is_primary;
|
||||||
MonitorHandle::from_repr(
|
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
|
||||||
self,
|
.map(|monitor_id| available.push(monitor_id));
|
||||||
resources,
|
|
||||||
monitor_index as u32,
|
|
||||||
monitor.into(),
|
|
||||||
is_primary,
|
|
||||||
)
|
|
||||||
.map(|monitor_id| available.push(monitor_id));
|
|
||||||
}
|
|
||||||
(xrandr_1_5.XRRFreeMonitors)(monitors);
|
|
||||||
} else {
|
|
||||||
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
|
|
||||||
// videowall setups will also show monitors that aren't in the logical groups the user
|
|
||||||
// cares about.
|
|
||||||
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
|
||||||
available = Vec::with_capacity((*resources).ncrtc as usize);
|
|
||||||
for crtc_index in 0..(*resources).ncrtc {
|
|
||||||
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
|
|
||||||
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
|
||||||
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
|
|
||||||
if is_active {
|
|
||||||
let crtc = util::MonitorRepr::from(crtc);
|
|
||||||
let is_primary = crtc.get_output() == primary;
|
|
||||||
has_primary |= is_primary;
|
|
||||||
MonitorHandle::from_repr(self, resources, crtc_id as u32, crtc, is_primary)
|
|
||||||
.map(|monitor_id| available.push(monitor_id));
|
|
||||||
}
|
|
||||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
|
||||||
}
|
}
|
||||||
|
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no monitors were detected as being primary, we just pick one ourselves!
|
// If no monitors were detected as being primary, we just pick one ourselves!
|
||||||
|
@ -232,23 +276,19 @@ impl XConnection {
|
||||||
self.available_monitors()
|
self.available_monitors()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|monitor| monitor.primary)
|
.find(|monitor| monitor.primary)
|
||||||
.expect("[winit] Failed to find any monitors using XRandR.")
|
.unwrap_or_else(MonitorHandle::dummy)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
|
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
|
||||||
{
|
let has_xrandr = unsafe {
|
||||||
let mut version_lock = XRANDR_VERSION.lock();
|
let mut major = 0;
|
||||||
if version_lock.is_none() {
|
let mut minor = 0;
|
||||||
let mut major = 0;
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
|
||||||
let mut minor = 0;
|
};
|
||||||
let has_extension =
|
assert!(
|
||||||
unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) };
|
has_xrandr == True,
|
||||||
if has_extension != True {
|
"[winit] XRandR extension not available."
|
||||||
panic!("[winit] XRandR extension not available.");
|
);
|
||||||
}
|
|
||||||
*version_lock = Some((major, minor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut event_offset = 0;
|
let mut event_offset = 0;
|
||||||
let mut error_offset = 0;
|
let mut error_offset = 0;
|
||||||
|
|
|
@ -30,13 +30,17 @@ impl XConnection {
|
||||||
event_mask: Option<c_long>,
|
event_mask: Option<c_long>,
|
||||||
data: ClientMsgPayload,
|
data: ClientMsgPayload,
|
||||||
) -> Flusher<'_> {
|
) -> Flusher<'_> {
|
||||||
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
|
let event = ffi::XClientMessageEvent {
|
||||||
event.type_ = ffi::ClientMessage;
|
type_: ffi::ClientMessage,
|
||||||
event.display = self.display;
|
display: self.display,
|
||||||
event.window = window;
|
window,
|
||||||
event.message_type = message_type;
|
message_type,
|
||||||
event.format = c_long::FORMAT as c_int;
|
format: c_long::FORMAT as c_int,
|
||||||
event.data = unsafe { mem::transmute(data) };
|
data: unsafe { mem::transmute(data) },
|
||||||
|
// These fields are ignored by `XSendEvent`
|
||||||
|
serial: 0,
|
||||||
|
send_event: 0,
|
||||||
|
};
|
||||||
self.send_event(target_window, event_mask, event)
|
self.send_event(target_window, event_mask, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,12 +58,17 @@ impl XConnection {
|
||||||
let format = T::FORMAT;
|
let format = T::FORMAT;
|
||||||
let size_of_t = mem::size_of::<T>();
|
let size_of_t = mem::size_of::<T>();
|
||||||
debug_assert_eq!(size_of_t, format.get_actual_size());
|
debug_assert_eq!(size_of_t, format.get_actual_size());
|
||||||
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
|
let mut event = ffi::XClientMessageEvent {
|
||||||
event.type_ = ffi::ClientMessage;
|
type_: ffi::ClientMessage,
|
||||||
event.display = self.display;
|
display: self.display,
|
||||||
event.window = window;
|
window,
|
||||||
event.message_type = message_type;
|
message_type,
|
||||||
event.format = format as c_int;
|
format: format as c_int,
|
||||||
|
data: ffi::ClientMessageData::new(),
|
||||||
|
// These fields are ignored by `XSendEvent`
|
||||||
|
serial: 0,
|
||||||
|
send_event: 0,
|
||||||
|
};
|
||||||
|
|
||||||
let t_per_payload = format.get_payload_size() / size_of_t;
|
let t_per_payload = format.get_payload_size() / size_of_t;
|
||||||
assert!(t_per_payload > 0);
|
assert!(t_per_payload > 0);
|
||||||
|
|
129
src/platform_impl/linux/x11/util/cursor.rs
Normal file
129
src/platform_impl/linux/x11/util/cursor.rs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
use crate::window::CursorIcon;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl XConnection {
|
||||||
|
pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option<CursorIcon>) {
|
||||||
|
let cursor = *self
|
||||||
|
.cursor_cache
|
||||||
|
.lock()
|
||||||
|
.entry(cursor)
|
||||||
|
.or_insert_with(|| self.get_cursor(cursor));
|
||||||
|
|
||||||
|
self.update_cursor(window, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_empty_cursor(&self) -> ffi::Cursor {
|
||||||
|
let data = 0;
|
||||||
|
let pixmap = unsafe {
|
||||||
|
let screen = (self.xlib.XDefaultScreen)(self.display);
|
||||||
|
let window = (self.xlib.XRootWindow)(self.display, screen);
|
||||||
|
(self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
if pixmap == 0 {
|
||||||
|
panic!("failed to allocate pixmap for cursor");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// We don't care about this color, since it only fills bytes
|
||||||
|
// in the pixmap which are not 0 in the mask.
|
||||||
|
let mut dummy_color = MaybeUninit::uninit();
|
||||||
|
let cursor = (self.xlib.XCreatePixmapCursor)(
|
||||||
|
self.display,
|
||||||
|
pixmap,
|
||||||
|
pixmap,
|
||||||
|
dummy_color.as_mut_ptr(),
|
||||||
|
dummy_color.as_mut_ptr(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
(self.xlib.XFreePixmap)(self.display, pixmap);
|
||||||
|
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_cursor(&self, name: &[u8]) -> ffi::Cursor {
|
||||||
|
unsafe {
|
||||||
|
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor {
|
||||||
|
for name in names.iter() {
|
||||||
|
let xcursor = self.load_cursor(name);
|
||||||
|
if xcursor != 0 {
|
||||||
|
return xcursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cursor(&self, cursor: Option<CursorIcon>) -> ffi::Cursor {
|
||||||
|
let cursor = match cursor {
|
||||||
|
Some(cursor) => cursor,
|
||||||
|
None => return self.create_empty_cursor(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let load = |name: &[u8]| self.load_cursor(name);
|
||||||
|
|
||||||
|
let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names);
|
||||||
|
|
||||||
|
// Try multiple names in some cases where the name
|
||||||
|
// differs on the desktop environments or themes.
|
||||||
|
//
|
||||||
|
// Try the better looking (or more suiting) names first.
|
||||||
|
match cursor {
|
||||||
|
CursorIcon::Alias => load(b"link\0"),
|
||||||
|
CursorIcon::Arrow => load(b"arrow\0"),
|
||||||
|
CursorIcon::Cell => load(b"plus\0"),
|
||||||
|
CursorIcon::Copy => load(b"copy\0"),
|
||||||
|
CursorIcon::Crosshair => load(b"crosshair\0"),
|
||||||
|
CursorIcon::Default => load(b"left_ptr\0"),
|
||||||
|
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
|
||||||
|
CursorIcon::Help => load(b"question_arrow\0"),
|
||||||
|
CursorIcon::Move => load(b"move\0"),
|
||||||
|
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
|
||||||
|
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
|
||||||
|
CursorIcon::Progress => load(b"left_ptr_watch\0"),
|
||||||
|
CursorIcon::AllScroll => load(b"all-scroll\0"),
|
||||||
|
CursorIcon::ContextMenu => load(b"context-menu\0"),
|
||||||
|
|
||||||
|
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
|
||||||
|
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
|
||||||
|
|
||||||
|
// Resize cursors
|
||||||
|
CursorIcon::EResize => load(b"right_side\0"),
|
||||||
|
CursorIcon::NResize => load(b"top_side\0"),
|
||||||
|
CursorIcon::NeResize => load(b"top_right_corner\0"),
|
||||||
|
CursorIcon::NwResize => load(b"top_left_corner\0"),
|
||||||
|
CursorIcon::SResize => load(b"bottom_side\0"),
|
||||||
|
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
|
||||||
|
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
|
||||||
|
CursorIcon::WResize => load(b"left_side\0"),
|
||||||
|
CursorIcon::EwResize => load(b"h_double_arrow\0"),
|
||||||
|
CursorIcon::NsResize => load(b"v_double_arrow\0"),
|
||||||
|
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
|
||||||
|
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
|
||||||
|
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
|
||||||
|
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
|
||||||
|
|
||||||
|
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
|
||||||
|
CursorIcon::VerticalText => load(b"vertical-text\0"),
|
||||||
|
|
||||||
|
CursorIcon::Wait => load(b"watch\0"),
|
||||||
|
|
||||||
|
CursorIcon::ZoomIn => load(b"zoom-in\0"),
|
||||||
|
CursorIcon::ZoomOut => load(b"zoom-out\0"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) {
|
||||||
|
unsafe {
|
||||||
|
(self.xlib.XDefineCursor)(self.display, window, cursor);
|
||||||
|
|
||||||
|
self.flush_requests().expect("Failed to set the cursor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,14 +41,14 @@ impl AaRect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TranslatedCoords {
|
pub struct TranslatedCoords {
|
||||||
pub x_rel_root: c_int,
|
pub x_rel_root: c_int,
|
||||||
pub y_rel_root: c_int,
|
pub y_rel_root: c_int,
|
||||||
pub child: ffi::Window,
|
pub child: ffi::Window,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Geometry {
|
pub struct Geometry {
|
||||||
pub root: ffi::Window,
|
pub root: ffi::Window,
|
||||||
// If you want positions relative to the root window, use translate_coords.
|
// If you want positions relative to the root window, use translate_coords.
|
||||||
|
@ -183,7 +183,8 @@ impl XConnection {
|
||||||
window: ffi::Window,
|
window: ffi::Window,
|
||||||
root: ffi::Window,
|
root: ffi::Window,
|
||||||
) -> Result<TranslatedCoords, XError> {
|
) -> Result<TranslatedCoords, XError> {
|
||||||
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
|
let mut coords = TranslatedCoords::default();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
(self.xlib.XTranslateCoordinates)(
|
(self.xlib.XTranslateCoordinates)(
|
||||||
self.display,
|
self.display,
|
||||||
|
@ -191,18 +192,20 @@ impl XConnection {
|
||||||
root,
|
root,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
&mut translated_coords.x_rel_root,
|
&mut coords.x_rel_root,
|
||||||
&mut translated_coords.y_rel_root,
|
&mut coords.y_rel_root,
|
||||||
&mut translated_coords.child,
|
&mut coords.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
|
|
||||||
self.check_errors().map(|_| translated_coords)
|
self.check_errors()?;
|
||||||
|
Ok(coords)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is adequate for inner_size
|
// This is adequate for inner_size
|
||||||
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
|
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
|
||||||
let mut geometry: Geometry = unsafe { mem::uninitialized() };
|
let mut geometry = Geometry::default();
|
||||||
|
|
||||||
let _status = unsafe {
|
let _status = unsafe {
|
||||||
(self.xlib.XGetGeometry)(
|
(self.xlib.XGetGeometry)(
|
||||||
self.display,
|
self.display,
|
||||||
|
@ -216,8 +219,9 @@ impl XConnection {
|
||||||
&mut geometry.depth,
|
&mut geometry.depth,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
//println!("XGetGeometry geo:{:?}", geometry);
|
|
||||||
self.check_errors().map(|_| geometry)
|
self.check_errors()?;
|
||||||
|
Ok(geometry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
|
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
|
||||||
|
@ -264,10 +268,10 @@ impl XConnection {
|
||||||
|
|
||||||
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
|
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
|
||||||
let parent = unsafe {
|
let parent = unsafe {
|
||||||
let mut root: ffi::Window = mem::uninitialized();
|
let mut root = 0;
|
||||||
let mut parent: ffi::Window = mem::uninitialized();
|
let mut parent = 0;
|
||||||
let mut children: *mut ffi::Window = ptr::null_mut();
|
let mut children: *mut ffi::Window = ptr::null_mut();
|
||||||
let mut nchildren: c_uint = mem::uninitialized();
|
let mut nchildren = 0;
|
||||||
|
|
||||||
// What's filled into `parent` if `window` is the root window?
|
// What's filled into `parent` if `window` is the root window?
|
||||||
let _status = (self.xlib.XQueryTree)(
|
let _status = (self.xlib.XQueryTree)(
|
||||||
|
|
|
@ -72,21 +72,21 @@ impl Default for WindowType {
|
||||||
impl WindowType {
|
impl WindowType {
|
||||||
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
|
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
|
||||||
use self::WindowType::*;
|
use self::WindowType::*;
|
||||||
let atom_name: &[u8] = match self {
|
let atom_name: &[u8] = match *self {
|
||||||
&Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
|
Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
|
||||||
&Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
|
Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
|
||||||
&Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
|
Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
|
||||||
&Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
|
Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
|
||||||
&Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
|
Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
|
||||||
&Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
|
Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
|
||||||
&Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
|
Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
|
||||||
&DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
|
DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
|
||||||
&PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
|
PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
|
||||||
&Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
|
Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
|
||||||
&Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
|
Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
|
||||||
&Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
|
Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
|
||||||
&Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
|
Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
|
||||||
&Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
|
Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
|
||||||
};
|
};
|
||||||
unsafe { xconn.get_atom_unchecked(atom_name) }
|
unsafe { xconn.get_atom_unchecked(atom_name) }
|
||||||
}
|
}
|
||||||
|
@ -317,13 +317,13 @@ impl XConnection {
|
||||||
|
|
||||||
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
|
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
|
||||||
let size_hints = self.alloc_size_hints();
|
let size_hints = self.alloc_size_hints();
|
||||||
let mut supplied_by_user: c_long = unsafe { mem::uninitialized() };
|
let mut supplied_by_user = MaybeUninit::uninit();
|
||||||
unsafe {
|
unsafe {
|
||||||
(self.xlib.XGetWMNormalHints)(
|
(self.xlib.XGetWMNormalHints)(
|
||||||
self.display,
|
self.display,
|
||||||
window,
|
window,
|
||||||
size_hints.ptr,
|
size_hints.ptr,
|
||||||
&mut supplied_by_user,
|
supplied_by_user.as_mut_ptr(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.check_errors().map(|_| NormalHints { size_hints })
|
self.check_errors().map(|_| NormalHints { size_hints })
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::str;
|
use std::{slice, str};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::event::ModifiersState;
|
use crate::event::ModifiersState;
|
||||||
|
@ -23,18 +23,19 @@ impl From<ffi::XIModifierState> for ModifiersState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Some of these fields are not used, but may be of use in the future.
|
||||||
pub struct PointerState<'a> {
|
pub struct PointerState<'a> {
|
||||||
xconn: &'a XConnection,
|
xconn: &'a XConnection,
|
||||||
root: ffi::Window,
|
pub root: ffi::Window,
|
||||||
child: ffi::Window,
|
pub child: ffi::Window,
|
||||||
pub root_x: c_double,
|
pub root_x: c_double,
|
||||||
pub root_y: c_double,
|
pub root_y: c_double,
|
||||||
win_x: c_double,
|
pub win_x: c_double,
|
||||||
win_y: c_double,
|
pub win_y: c_double,
|
||||||
buttons: ffi::XIButtonState,
|
buttons: ffi::XIButtonState,
|
||||||
modifiers: ffi::XIModifierState,
|
modifiers: ffi::XIModifierState,
|
||||||
group: ffi::XIGroupState,
|
pub group: ffi::XIGroupState,
|
||||||
relative_to_window: bool,
|
pub relative_to_window: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PointerState<'a> {
|
impl<'a> PointerState<'a> {
|
||||||
|
@ -93,29 +94,46 @@ impl XConnection {
|
||||||
device_id: c_int,
|
device_id: c_int,
|
||||||
) -> Result<PointerState<'_>, XError> {
|
) -> Result<PointerState<'_>, XError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut pointer_state: PointerState<'_> = mem::uninitialized();
|
let mut root = 0;
|
||||||
pointer_state.xconn = self;
|
let mut child = 0;
|
||||||
pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)(
|
let mut root_x = 0.0;
|
||||||
|
let mut root_y = 0.0;
|
||||||
|
let mut win_x = 0.0;
|
||||||
|
let mut win_y = 0.0;
|
||||||
|
let mut buttons = Default::default();
|
||||||
|
let mut modifiers = Default::default();
|
||||||
|
let mut group = Default::default();
|
||||||
|
|
||||||
|
let relative_to_window = (self.xinput2.XIQueryPointer)(
|
||||||
self.display,
|
self.display,
|
||||||
device_id,
|
device_id,
|
||||||
window,
|
window,
|
||||||
&mut pointer_state.root,
|
&mut root,
|
||||||
&mut pointer_state.child,
|
&mut child,
|
||||||
&mut pointer_state.root_x,
|
&mut root_x,
|
||||||
&mut pointer_state.root_y,
|
&mut root_y,
|
||||||
&mut pointer_state.win_x,
|
&mut win_x,
|
||||||
&mut pointer_state.win_y,
|
&mut win_y,
|
||||||
&mut pointer_state.buttons,
|
&mut buttons,
|
||||||
&mut pointer_state.modifiers,
|
&mut modifiers,
|
||||||
&mut pointer_state.group,
|
&mut group,
|
||||||
) == ffi::True;
|
) == ffi::True;
|
||||||
if let Err(err) = self.check_errors() {
|
|
||||||
// Running the destrutor would be bad news for us...
|
self.check_errors()?;
|
||||||
mem::forget(pointer_state);
|
|
||||||
Err(err)
|
Ok(PointerState {
|
||||||
} else {
|
xconn: self,
|
||||||
Ok(pointer_state)
|
root,
|
||||||
}
|
child,
|
||||||
|
root_x,
|
||||||
|
root_y,
|
||||||
|
win_x,
|
||||||
|
win_y,
|
||||||
|
buttons,
|
||||||
|
modifiers,
|
||||||
|
group,
|
||||||
|
relative_to_window,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +141,8 @@ impl XConnection {
|
||||||
&self,
|
&self,
|
||||||
ic: ffi::XIC,
|
ic: ffi::XIC,
|
||||||
key_event: &mut ffi::XKeyEvent,
|
key_event: &mut ffi::XKeyEvent,
|
||||||
buffer: &mut [u8],
|
buffer: *mut u8,
|
||||||
|
size: usize,
|
||||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||||
let mut keysym: ffi::KeySym = 0;
|
let mut keysym: ffi::KeySym = 0;
|
||||||
let mut status: ffi::Status = 0;
|
let mut status: ffi::Status = 0;
|
||||||
|
@ -131,8 +150,8 @@ impl XConnection {
|
||||||
(self.xlib.Xutf8LookupString)(
|
(self.xlib.Xutf8LookupString)(
|
||||||
ic,
|
ic,
|
||||||
key_event,
|
key_event,
|
||||||
buffer.as_mut_ptr() as *mut c_char,
|
buffer as *mut c_char,
|
||||||
buffer.len() as c_int,
|
size as c_int,
|
||||||
&mut keysym,
|
&mut keysym,
|
||||||
&mut status,
|
&mut status,
|
||||||
)
|
)
|
||||||
|
@ -141,21 +160,28 @@ impl XConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
||||||
let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() };
|
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||||
let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
|
// which do not require initialization.
|
||||||
// The buffer overflowed, so we'll make a new one on the heap.
|
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
|
||||||
if status == ffi::XBufferOverflow {
|
unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
let mut buffer = Vec::with_capacity(count as usize);
|
// If the buffer overflows, we'll make a new one on the heap.
|
||||||
unsafe { buffer.set_len(count as usize) };
|
let mut vec;
|
||||||
let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
|
|
||||||
|
let (_, status, count) =
|
||||||
|
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
|
||||||
|
|
||||||
|
let bytes = if status == ffi::XBufferOverflow {
|
||||||
|
vec = Vec::with_capacity(count as usize);
|
||||||
|
let (_, _, new_count) =
|
||||||
|
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
|
||||||
debug_assert_eq!(count, new_count);
|
debug_assert_eq!(count, new_count);
|
||||||
str::from_utf8(&buffer[..count as usize])
|
|
||||||
.unwrap_or("")
|
unsafe { vec.set_len(count as usize) };
|
||||||
.to_string()
|
&vec[..count as usize]
|
||||||
} else {
|
} else {
|
||||||
str::from_utf8(&buffer[..count as usize])
|
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
|
||||||
.unwrap_or("")
|
};
|
||||||
.to_string()
|
|
||||||
}
|
str::from_utf8(bytes).unwrap_or("").to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
|
|
||||||
mod atom;
|
mod atom;
|
||||||
mod client_msg;
|
mod client_msg;
|
||||||
|
mod cursor;
|
||||||
mod format;
|
mod format;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
mod hint;
|
mod hint;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod input;
|
mod input;
|
||||||
mod memory;
|
mod memory;
|
||||||
|
pub mod modifiers;
|
||||||
mod randr;
|
mod randr;
|
||||||
mod window_property;
|
mod window_property;
|
||||||
mod wm;
|
mod wm;
|
||||||
|
@ -18,7 +20,12 @@ pub use self::{
|
||||||
randr::*, window_property::*, wm::*,
|
randr::*, window_property::*, wm::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{mem, ops::BitAnd, os::raw::*, ptr};
|
use std::{
|
||||||
|
mem::{self, MaybeUninit},
|
||||||
|
ops::BitAnd,
|
||||||
|
os::raw::*,
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{ffi, XConnection, XError};
|
use super::{ffi, XConnection, XError};
|
||||||
|
|
||||||
|
|
149
src/platform_impl/linux/x11/util/modifiers.rs
Normal file
149
src/platform_impl/linux/x11/util/modifiers.rs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
use std::{collections::HashMap, slice};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::event::{ElementState, ModifiersState};
|
||||||
|
|
||||||
|
// Offsets within XModifierKeymap to each set of keycodes.
|
||||||
|
// We are only interested in Shift, Control, Alt, and Logo.
|
||||||
|
//
|
||||||
|
// There are 8 sets total. The order of keycode sets is:
|
||||||
|
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
|
||||||
|
//
|
||||||
|
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
|
||||||
|
const SHIFT_OFFSET: usize = 0;
|
||||||
|
const CONTROL_OFFSET: usize = 2;
|
||||||
|
const ALT_OFFSET: usize = 3;
|
||||||
|
const LOGO_OFFSET: usize = 6;
|
||||||
|
const NUM_MODS: usize = 8;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Modifier {
|
||||||
|
Alt,
|
||||||
|
Ctrl,
|
||||||
|
Shift,
|
||||||
|
Logo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ModifierKeymap {
|
||||||
|
// Maps keycodes to modifiers
|
||||||
|
keys: HashMap<ffi::KeyCode, Modifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct ModifierKeyState {
|
||||||
|
// Contains currently pressed modifier keys and their corresponding modifiers
|
||||||
|
keys: HashMap<ffi::KeyCode, Modifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModifierKeymap {
|
||||||
|
pub fn new() -> ModifierKeymap {
|
||||||
|
ModifierKeymap::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option<Modifier> {
|
||||||
|
self.keys.get(&keycode).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_from_x_connection(&mut self, xconn: &XConnection) {
|
||||||
|
unsafe {
|
||||||
|
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
|
||||||
|
|
||||||
|
if keymap.is_null() {
|
||||||
|
panic!("failed to allocate XModifierKeymap");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reset_from_x_keymap(&*keymap);
|
||||||
|
|
||||||
|
(xconn.xlib.XFreeModifiermap)(keymap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) {
|
||||||
|
let keys_per_mod = keymap.max_keypermod as usize;
|
||||||
|
|
||||||
|
let keys = unsafe {
|
||||||
|
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.keys.clear();
|
||||||
|
|
||||||
|
self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift);
|
||||||
|
self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl);
|
||||||
|
self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt);
|
||||||
|
self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_x_keys(
|
||||||
|
&mut self,
|
||||||
|
keys: &[ffi::KeyCode],
|
||||||
|
offset: usize,
|
||||||
|
keys_per_mod: usize,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
let start = offset * keys_per_mod;
|
||||||
|
let end = start + keys_per_mod;
|
||||||
|
|
||||||
|
for &keycode in &keys[start..end] {
|
||||||
|
if keycode != 0 {
|
||||||
|
self.keys.insert(keycode, modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModifierKeyState {
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.keys.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.keys.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, mods: &ModifierKeymap) {
|
||||||
|
self.keys.retain(|k, v| {
|
||||||
|
if let Some(m) = mods.get_modifier(*k) {
|
||||||
|
*v = m;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modifiers(&self) -> ModifiersState {
|
||||||
|
let mut state = ModifiersState::default();
|
||||||
|
|
||||||
|
for &m in self.keys.values() {
|
||||||
|
set_modifier(&mut state, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) {
|
||||||
|
match state {
|
||||||
|
ElementState::Pressed => self.key_press(keycode, modifier),
|
||||||
|
ElementState::Released => self.key_release(keycode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) {
|
||||||
|
self.keys.insert(keycode, modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
|
||||||
|
self.keys.remove(&keycode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_modifier(state: &mut ModifiersState, modifier: Modifier) {
|
||||||
|
match modifier {
|
||||||
|
Modifier::Alt => state.alt = true,
|
||||||
|
Modifier::Ctrl => state.ctrl = true,
|
||||||
|
Modifier::Shift => state.shift = true,
|
||||||
|
Modifier::Logo => state.logo = true,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
use std::{env, slice, str::FromStr};
|
use std::{env, slice, str::FromStr};
|
||||||
|
|
||||||
use super::*;
|
use super::{
|
||||||
use crate::{dpi::validate_hidpi_factor, monitor::VideoMode};
|
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
|
||||||
|
|
||||||
pub fn calc_dpi_factor(
|
pub fn calc_dpi_factor(
|
||||||
(width_px, height_px): (u32, u32),
|
(width_px, height_px): (u32, u32),
|
||||||
|
@ -34,47 +37,6 @@ pub fn calc_dpi_factor(
|
||||||
dpi_factor
|
dpi_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MonitorRepr {
|
|
||||||
Monitor(*mut ffi::XRRMonitorInfo),
|
|
||||||
Crtc(*mut ffi::XRRCrtcInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorRepr {
|
|
||||||
pub unsafe fn get_output(&self) -> ffi::RROutput {
|
|
||||||
match *self {
|
|
||||||
// Same member names, but different locations within the struct...
|
|
||||||
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
|
|
||||||
MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn size(&self) -> (u32, u32) {
|
|
||||||
match *self {
|
|
||||||
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
|
|
||||||
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn position(&self) -> (i32, i32) {
|
|
||||||
match *self {
|
|
||||||
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
|
|
||||||
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
|
|
||||||
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
|
|
||||||
MonitorRepr::Monitor(monitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
|
|
||||||
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
|
|
||||||
MonitorRepr::Crtc(crtc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XConnection {
|
impl XConnection {
|
||||||
// Retrieve DPI from Xft.dpi property
|
// Retrieve DPI from Xft.dpi property
|
||||||
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
||||||
|
@ -96,11 +58,11 @@ impl XConnection {
|
||||||
}
|
}
|
||||||
pub unsafe fn get_output_info(
|
pub unsafe fn get_output_info(
|
||||||
&self,
|
&self,
|
||||||
resources: *mut ffi::XRRScreenResources,
|
resources: *mut XRRScreenResources,
|
||||||
repr: &MonitorRepr,
|
crtc: *mut XRRCrtcInfo,
|
||||||
) -> Option<(String, f64, Vec<VideoMode>)> {
|
) -> Option<(String, f64, Vec<VideoMode>)> {
|
||||||
let output_info =
|
let output_info =
|
||||||
(self.xrandr.XRRGetOutputInfo)(self.display, resources, repr.get_output());
|
(self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0));
|
||||||
if output_info.is_null() {
|
if output_info.is_null() {
|
||||||
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
||||||
// it's possible for it to return null.
|
// it's possible for it to return null.
|
||||||
|
@ -132,6 +94,10 @@ impl XConnection {
|
||||||
size: (x.width, x.height),
|
size: (x.width, x.height),
|
||||||
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
|
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
|
||||||
bit_depth: bit_depth as u16,
|
bit_depth: bit_depth as u16,
|
||||||
|
native_mode: x.id,
|
||||||
|
// This is populated in `MonitorHandle::video_modes` as the
|
||||||
|
// video mode is returned to the user
|
||||||
|
monitor: None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -144,7 +110,7 @@ impl XConnection {
|
||||||
dpi / 96.
|
dpi / 96.
|
||||||
} else {
|
} else {
|
||||||
calc_dpi_factor(
|
calc_dpi_factor(
|
||||||
repr.size(),
|
((*crtc).width as u32, (*crtc).height as u32),
|
||||||
(
|
(
|
||||||
(*output_info).mm_width as u64,
|
(*output_info).mm_width as u64,
|
||||||
(*output_info).mm_height as u64,
|
(*output_info).mm_height as u64,
|
||||||
|
@ -155,4 +121,61 @@ impl XConnection {
|
||||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
||||||
Some((name, hidpi_factor, modes.collect()))
|
Some((name, hidpi_factor, modes.collect()))
|
||||||
}
|
}
|
||||||
|
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
|
||||||
|
unsafe {
|
||||||
|
let mut major = 0;
|
||||||
|
let mut minor = 0;
|
||||||
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
||||||
|
|
||||||
|
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||||
|
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
||||||
|
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||||
|
} else {
|
||||||
|
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
||||||
|
};
|
||||||
|
|
||||||
|
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||||
|
let status = (self.xrandr.XRRSetCrtcConfig)(
|
||||||
|
self.display,
|
||||||
|
resources,
|
||||||
|
crtc_id,
|
||||||
|
CurrentTime,
|
||||||
|
(*crtc).x,
|
||||||
|
(*crtc).y,
|
||||||
|
mode_id,
|
||||||
|
(*crtc).rotation,
|
||||||
|
(*crtc).outputs.offset(0),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||||
|
(self.xrandr.XRRFreeScreenResources)(resources);
|
||||||
|
|
||||||
|
if status == Success as i32 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
|
||||||
|
unsafe {
|
||||||
|
let mut major = 0;
|
||||||
|
let mut minor = 0;
|
||||||
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
||||||
|
|
||||||
|
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||||
|
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
||||||
|
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||||
|
} else {
|
||||||
|
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
||||||
|
};
|
||||||
|
|
||||||
|
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||||
|
let mode = (*crtc).mode;
|
||||||
|
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||||
|
(self.xrandr.XRRFreeScreenResources)(resources);
|
||||||
|
mode
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,13 +43,14 @@ impl XConnection {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
let mut done = false;
|
let mut done = false;
|
||||||
|
let mut actual_type = 0;
|
||||||
|
let mut actual_format = 0;
|
||||||
|
let mut quantity_returned = 0;
|
||||||
|
let mut bytes_after = 0;
|
||||||
|
let mut buf: *mut c_uchar = ptr::null_mut();
|
||||||
|
|
||||||
while !done {
|
while !done {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut actual_type: ffi::Atom = mem::uninitialized();
|
|
||||||
let mut actual_format: c_int = mem::uninitialized();
|
|
||||||
let mut quantity_returned: c_ulong = mem::uninitialized();
|
|
||||||
let mut bytes_after: c_ulong = mem::uninitialized();
|
|
||||||
let mut buf: *mut c_uchar = ptr::null_mut();
|
|
||||||
(self.xlib.XGetWindowProperty)(
|
(self.xlib.XGetWindowProperty)(
|
||||||
self.display,
|
self.display,
|
||||||
window,
|
window,
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
use std::{cmp, collections::HashSet, env, ffi::CString, mem, os::raw::*, path::Path, sync::Arc};
|
use raw_window_handle::unix::X11Handle;
|
||||||
|
use std::{
|
||||||
|
cmp,
|
||||||
|
collections::HashSet,
|
||||||
|
env,
|
||||||
|
ffi::CString,
|
||||||
|
mem::{self, replace, MaybeUninit},
|
||||||
|
os::raw::*,
|
||||||
|
path::Path,
|
||||||
|
ptr, slice,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -6,12 +17,13 @@ use parking_lot::Mutex;
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
platform_impl::{
|
platform_impl::{
|
||||||
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
|
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
|
||||||
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
|
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
|
||||||
|
VideoMode as PlatformVideoMode,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, Icon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
|
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
|
||||||
|
@ -36,18 +48,22 @@ pub struct SharedState {
|
||||||
pub guessed_dpi: Option<f64>,
|
pub guessed_dpi: Option<f64>,
|
||||||
pub last_monitor: Option<X11MonitorHandle>,
|
pub last_monitor: Option<X11MonitorHandle>,
|
||||||
pub dpi_adjusted: Option<(f64, f64)>,
|
pub dpi_adjusted: Option<(f64, f64)>,
|
||||||
pub fullscreen: Option<RootMonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
// Used to restore position after exiting fullscreen.
|
// Used to restore position after exiting fullscreen
|
||||||
pub restore_position: Option<(i32, i32)>,
|
pub restore_position: Option<(i32, i32)>,
|
||||||
|
// Used to restore video mode after exiting fullscreen
|
||||||
|
pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>,
|
||||||
pub frame_extents: Option<util::FrameExtentsHeuristic>,
|
pub frame_extents: Option<util::FrameExtentsHeuristic>,
|
||||||
pub min_inner_size: Option<LogicalSize>,
|
pub min_inner_size: Option<LogicalSize>,
|
||||||
pub max_inner_size: Option<LogicalSize>,
|
pub max_inner_size: Option<LogicalSize>,
|
||||||
|
pub is_visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
fn new(dpi_factor: f64) -> Mutex<Self> {
|
fn new(dpi_factor: f64, is_visible: bool) -> Mutex<Self> {
|
||||||
let mut shared_state = SharedState::default();
|
let mut shared_state = SharedState::default();
|
||||||
shared_state.guessed_dpi = Some(dpi_factor);
|
shared_state.guessed_dpi = Some(dpi_factor);
|
||||||
|
shared_state.is_visible = is_visible;
|
||||||
Mutex::new(shared_state)
|
Mutex::new(shared_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +119,7 @@ impl UnownedWindow {
|
||||||
.unwrap_or(1.0)
|
.unwrap_or(1.0)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Err(os_error!(OsError::XMisc("No monitors were detected.")));
|
1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Guessed window DPI factor: {}", dpi_factor);
|
info!("Guessed window DPI factor: {}", dpi_factor);
|
||||||
|
@ -216,7 +232,7 @@ impl UnownedWindow {
|
||||||
cursor_grabbed: Mutex::new(false),
|
cursor_grabbed: Mutex::new(false),
|
||||||
cursor_visible: Mutex::new(true),
|
cursor_visible: Mutex::new(true),
|
||||||
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
|
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
|
||||||
shared_state: SharedState::new(dpi_factor),
|
shared_state: SharedState::new(dpi_factor, window_attrs.visible),
|
||||||
pending_redraws: event_loop.pending_redraws.clone(),
|
pending_redraws: event_loop.pending_redraws.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -282,9 +298,7 @@ impl UnownedWindow {
|
||||||
|
|
||||||
window.set_pid().map(|flusher| flusher.queue());
|
window.set_pid().map(|flusher| flusher.queue());
|
||||||
|
|
||||||
if pl_attribs.x11_window_type != Default::default() {
|
window.set_window_types(pl_attribs.x11_window_types).queue();
|
||||||
window.set_window_type(pl_attribs.x11_window_type).queue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(variant) = pl_attribs.gtk_theme_variant {
|
if let Some(variant) = pl_attribs.gtk_theme_variant {
|
||||||
window.set_gtk_theme_variant(variant).queue();
|
window.set_gtk_theme_variant(variant).queue();
|
||||||
|
@ -341,6 +355,8 @@ impl UnownedWindow {
|
||||||
unsafe {
|
unsafe {
|
||||||
(xconn.xlib.XMapRaised)(xconn.display, window.xwindow);
|
(xconn.xlib.XMapRaised)(xconn.display, window.xwindow);
|
||||||
} //.queue();
|
} //.queue();
|
||||||
|
|
||||||
|
window.wait_for_visibility_notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to make keyboard input repeat detectable
|
// Attempt to make keyboard input repeat detectable
|
||||||
|
@ -398,6 +414,7 @@ impl UnownedWindow {
|
||||||
if window_attrs.fullscreen.is_some() {
|
if window_attrs.fullscreen.is_some() {
|
||||||
window
|
window
|
||||||
.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
||||||
|
.unwrap()
|
||||||
.queue();
|
.queue();
|
||||||
}
|
}
|
||||||
if window_attrs.always_on_top {
|
if window_attrs.always_on_top {
|
||||||
|
@ -405,27 +422,6 @@ impl UnownedWindow {
|
||||||
.set_always_on_top_inner(window_attrs.always_on_top)
|
.set_always_on_top_inner(window_attrs.always_on_top)
|
||||||
.queue();
|
.queue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if window_attrs.visible {
|
|
||||||
unsafe {
|
|
||||||
// XSetInputFocus generates an error if the window is not visible, so we wait
|
|
||||||
// until we receive VisibilityNotify.
|
|
||||||
let mut event = mem::uninitialized();
|
|
||||||
(xconn.xlib.XIfEvent)(
|
|
||||||
// This will flush the request buffer IF it blocks.
|
|
||||||
xconn.display,
|
|
||||||
&mut event as *mut ffi::XEvent,
|
|
||||||
Some(visibility_predicate),
|
|
||||||
window.xwindow as _,
|
|
||||||
);
|
|
||||||
(xconn.xlib.XSetInputFocus)(
|
|
||||||
xconn.display,
|
|
||||||
window.xwindow,
|
|
||||||
ffi::RevertToParent,
|
|
||||||
ffi::CurrentTime,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We never want to give the user a broken window, since by then, it's too late to handle.
|
// We never want to give the user a broken window, since by then, it's too late to handle.
|
||||||
|
@ -449,19 +445,22 @@ impl UnownedWindow {
|
||||||
let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") };
|
let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") };
|
||||||
let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") };
|
let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") };
|
||||||
unsafe {
|
unsafe {
|
||||||
let (hostname, hostname_length) = {
|
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
|
||||||
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
|
// the limit defined by OpenBSD.
|
||||||
// the limit defined by OpenBSD.
|
const MAXHOSTNAMELEN: usize = 256;
|
||||||
const MAXHOSTNAMELEN: usize = 256;
|
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||||
let mut hostname: [c_char; MAXHOSTNAMELEN] = mem::uninitialized();
|
// which do not require initialization.
|
||||||
let status = libc::gethostname(hostname.as_mut_ptr(), hostname.len());
|
let mut buffer: [MaybeUninit<c_char>; MAXHOSTNAMELEN] =
|
||||||
if status != 0 {
|
MaybeUninit::uninit().assume_init();
|
||||||
return None;
|
let status = libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len());
|
||||||
}
|
if status != 0 {
|
||||||
hostname[MAXHOSTNAMELEN - 1] = '\0' as c_char; // a little extra safety
|
return None;
|
||||||
let hostname_length = libc::strlen(hostname.as_ptr());
|
}
|
||||||
(hostname, hostname_length as usize)
|
ptr::write(buffer[MAXHOSTNAMELEN - 1].as_mut_ptr() as *mut u8, b'\0'); // a little extra safety
|
||||||
};
|
let hostname_length = libc::strlen(buffer.as_ptr() as *const c_char);
|
||||||
|
|
||||||
|
let hostname = slice::from_raw_parts(buffer.as_ptr() as *const c_char, hostname_length);
|
||||||
|
|
||||||
self.xconn
|
self.xconn
|
||||||
.change_property(
|
.change_property(
|
||||||
self.xwindow,
|
self.xwindow,
|
||||||
|
@ -482,15 +481,19 @@ impl UnownedWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_window_type(&self, window_type: util::WindowType) -> util::Flusher<'_> {
|
fn set_window_types(&self, window_types: Vec<util::WindowType>) -> util::Flusher<'_> {
|
||||||
let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") };
|
let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") };
|
||||||
let window_type_atom = window_type.as_atom(&self.xconn);
|
let atoms: Vec<_> = window_types
|
||||||
|
.iter()
|
||||||
|
.map(|t| t.as_atom(&self.xconn))
|
||||||
|
.collect();
|
||||||
|
|
||||||
self.xconn.change_property(
|
self.xconn.change_property(
|
||||||
self.xwindow,
|
self.xwindow,
|
||||||
hint_atom,
|
hint_atom,
|
||||||
ffi::XA_ATOM,
|
ffi::XA_ATOM,
|
||||||
util::PropMode::Replace,
|
util::PropMode::Replace,
|
||||||
&[window_type_atom],
|
&atoms,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,44 +551,151 @@ impl UnownedWindow {
|
||||||
fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> {
|
fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> {
|
||||||
let fullscreen_atom =
|
let fullscreen_atom =
|
||||||
unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") };
|
unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") };
|
||||||
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0))
|
let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0));
|
||||||
|
|
||||||
|
if fullscreen {
|
||||||
|
// Ensure that the fullscreen window receives input focus to prevent
|
||||||
|
// locking up the user's display.
|
||||||
|
unsafe {
|
||||||
|
(self.xconn.xlib.XSetInputFocus)(
|
||||||
|
self.xconn.display,
|
||||||
|
self.xwindow,
|
||||||
|
ffi::RevertToParent,
|
||||||
|
ffi::CurrentTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flusher
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_fullscreen_inner(&self, monitor: Option<RootMonitorHandle>) -> util::Flusher<'_> {
|
fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
|
||||||
match monitor {
|
let mut shared_state_lock = self.shared_state.lock();
|
||||||
|
|
||||||
|
if !shared_state_lock.is_visible {
|
||||||
|
// Setting fullscreen on a window that is not visible will generate an error.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_fullscreen = shared_state_lock.fullscreen.clone();
|
||||||
|
if old_fullscreen == fullscreen {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
shared_state_lock.fullscreen = fullscreen.clone();
|
||||||
|
|
||||||
|
match (&old_fullscreen, &fullscreen) {
|
||||||
|
// Store the desktop video mode before entering exclusive
|
||||||
|
// fullscreen, so we can restore it upon exit, as XRandR does not
|
||||||
|
// provide a mechanism to set this per app-session or restore this
|
||||||
|
// to the desktop video mode as macOS and Windows do
|
||||||
|
(
|
||||||
|
&None,
|
||||||
|
&Some(Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(ref video_mode),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
&Some(Fullscreen::Borderless(_)),
|
||||||
|
&Some(Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(ref video_mode),
|
||||||
|
})),
|
||||||
|
) => {
|
||||||
|
let monitor = video_mode.monitor.as_ref().unwrap();
|
||||||
|
shared_state_lock.desktop_video_mode =
|
||||||
|
Some((monitor.id, self.xconn.get_crtc_mode(monitor.id)));
|
||||||
|
}
|
||||||
|
// Restore desktop video mode upon exiting exclusive fullscreen
|
||||||
|
(&Some(Fullscreen::Exclusive(_)), &None)
|
||||||
|
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
|
||||||
|
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
|
||||||
|
self.xconn
|
||||||
|
.set_crtc_config(monitor_id, mode_id)
|
||||||
|
.expect("failed to restore desktop video mode");
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(shared_state_lock);
|
||||||
|
|
||||||
|
match fullscreen {
|
||||||
None => {
|
None => {
|
||||||
let flusher = self.set_fullscreen_hint(false);
|
let flusher = self.set_fullscreen_hint(false);
|
||||||
if let Some(position) = self.shared_state.lock().restore_position.take() {
|
let mut shared_state_lock = self.shared_state.lock();
|
||||||
|
if let Some(position) = shared_state_lock.restore_position.take() {
|
||||||
self.set_position_inner(position.0, position.1).queue();
|
self.set_position_inner(position.0, position.1).queue();
|
||||||
}
|
}
|
||||||
flusher
|
Some(flusher)
|
||||||
}
|
}
|
||||||
Some(RootMonitorHandle {
|
Some(fullscreen) => {
|
||||||
inner: PlatformMonitorHandle::X(monitor),
|
let (video_mode, monitor) = match fullscreen {
|
||||||
}) => {
|
Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(ref video_mode),
|
||||||
|
}) => (Some(video_mode), video_mode.monitor.as_ref().unwrap()),
|
||||||
|
Fullscreen::Borderless(RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::X(ref monitor),
|
||||||
|
}) => (None, monitor),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't set fullscreen on an invalid dummy monitor handle
|
||||||
|
if monitor.id == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(video_mode) = video_mode {
|
||||||
|
// FIXME: this is actually not correct if we're setting the
|
||||||
|
// video mode to a resolution higher than the current
|
||||||
|
// desktop resolution, because XRandR does not automatically
|
||||||
|
// reposition the monitors to the right and below this
|
||||||
|
// monitor.
|
||||||
|
//
|
||||||
|
// What ends up happening is we will get the fullscreen
|
||||||
|
// window showing up on those monitors as well, because
|
||||||
|
// their virtual position now overlaps with the monitor that
|
||||||
|
// we just made larger..
|
||||||
|
//
|
||||||
|
// It'd be quite a bit of work to handle this correctly (and
|
||||||
|
// nobody else seems to bother doing this correctly either),
|
||||||
|
// so we're just leaving this broken. Fixing this would
|
||||||
|
// involve storing all CRTCs upon entering fullscreen,
|
||||||
|
// restoring them upon exit, and after entering fullscreen,
|
||||||
|
// repositioning displays to the right and below this
|
||||||
|
// display. I think there would still be edge cases that are
|
||||||
|
// difficult or impossible to handle correctly, e.g. what if
|
||||||
|
// a new monitor was plugged in while in fullscreen?
|
||||||
|
//
|
||||||
|
// I think we might just want to disallow setting the video
|
||||||
|
// mode higher than the current desktop video mode (I'm sure
|
||||||
|
// this will make someone unhappy, but it's very unusual for
|
||||||
|
// games to want to do this anyway).
|
||||||
|
self.xconn
|
||||||
|
.set_crtc_config(monitor.id, video_mode.native_mode)
|
||||||
|
.expect("failed to set video mode");
|
||||||
|
}
|
||||||
|
|
||||||
let window_position = self.outer_position_physical();
|
let window_position = self.outer_position_physical();
|
||||||
self.shared_state.lock().restore_position = Some(window_position);
|
self.shared_state.lock().restore_position = Some(window_position);
|
||||||
let monitor_origin: (i32, i32) = monitor.position().into();
|
let monitor_origin: (i32, i32) = monitor.position().into();
|
||||||
self.set_position_inner(monitor_origin.0, monitor_origin.1)
|
self.set_position_inner(monitor_origin.0, monitor_origin.1)
|
||||||
.queue();
|
.queue();
|
||||||
self.set_fullscreen_hint(true)
|
Some(self.set_fullscreen_hint(true))
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
self.shared_state.lock().fullscreen.clone()
|
self.shared_state.lock().fullscreen.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
self.shared_state.lock().fullscreen = monitor.clone();
|
if let Some(flusher) = self.set_fullscreen_inner(fullscreen) {
|
||||||
self.set_fullscreen_inner(monitor)
|
flusher
|
||||||
.flush()
|
.sync()
|
||||||
.expect("Failed to change window fullscreen state");
|
.expect("Failed to change window fullscreen state");
|
||||||
self.invalidate_cached_frame_extents();
|
self.invalidate_cached_frame_extents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rect(&self) -> util::AaRect {
|
fn get_rect(&self) -> util::AaRect {
|
||||||
|
@ -599,11 +709,11 @@ impl UnownedWindow {
|
||||||
pub fn current_monitor(&self) -> X11MonitorHandle {
|
pub fn current_monitor(&self) -> X11MonitorHandle {
|
||||||
let monitor = self.shared_state.lock().last_monitor.as_ref().cloned();
|
let monitor = self.shared_state.lock().last_monitor.as_ref().cloned();
|
||||||
monitor.unwrap_or_else(|| {
|
monitor.unwrap_or_else(|| {
|
||||||
let monitor = self
|
let monitor = self.xconn.get_monitor_for_window(Some(self.get_rect()));
|
||||||
.xconn
|
// Avoid caching an invalid dummy monitor handle
|
||||||
.get_monitor_for_window(Some(self.get_rect()))
|
if monitor.id != 0 {
|
||||||
.to_owned();
|
self.shared_state.lock().last_monitor = Some(monitor.clone());
|
||||||
self.shared_state.lock().last_monitor = Some(monitor.clone());
|
}
|
||||||
monitor
|
monitor
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -738,12 +848,22 @@ impl UnownedWindow {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_visible(&self, visible: bool) {
|
pub fn set_visible(&self, visible: bool) {
|
||||||
|
let is_visible = self.shared_state.lock().is_visible;
|
||||||
|
|
||||||
|
if visible == is_visible {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match visible {
|
match visible {
|
||||||
true => unsafe {
|
true => unsafe {
|
||||||
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
|
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
|
||||||
self.xconn
|
self.xconn
|
||||||
.flush_requests()
|
.flush_requests()
|
||||||
.expect("Failed to call XMapRaised");
|
.expect("Failed to call XMapRaised");
|
||||||
|
|
||||||
|
// Some X requests may generate an error if the window is not
|
||||||
|
// visible, so we must wait until the window becomes visible.
|
||||||
|
self.wait_for_visibility_notify();
|
||||||
},
|
},
|
||||||
false => unsafe {
|
false => unsafe {
|
||||||
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
|
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
|
||||||
|
@ -752,6 +872,21 @@ impl UnownedWindow {
|
||||||
.expect("Failed to call XUnmapWindow");
|
.expect("Failed to call XUnmapWindow");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.shared_state.lock().is_visible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for_visibility_notify(&self) {
|
||||||
|
unsafe {
|
||||||
|
let mut event = MaybeUninit::uninit();
|
||||||
|
|
||||||
|
(self.xconn.xlib.XIfEvent)(
|
||||||
|
self.xconn.display,
|
||||||
|
event.as_mut_ptr(),
|
||||||
|
Some(visibility_predicate),
|
||||||
|
self.xwindow as _,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_cached_frame_extents(&self) {
|
fn update_cached_frame_extents(&self) {
|
||||||
|
@ -1025,131 +1160,14 @@ impl UnownedWindow {
|
||||||
unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ }
|
unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_cursor(&self, name: &[u8]) -> ffi::Cursor {
|
|
||||||
unsafe {
|
|
||||||
(self.xconn.xcursor.XcursorLibraryLoadCursor)(
|
|
||||||
self.xconn.display,
|
|
||||||
name.as_ptr() as *const c_char,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor {
|
|
||||||
for name in names.iter() {
|
|
||||||
let xcursor = self.load_cursor(name);
|
|
||||||
if xcursor != 0 {
|
|
||||||
return xcursor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cursor(&self, cursor: CursorIcon) -> ffi::Cursor {
|
|
||||||
let load = |name: &[u8]| self.load_cursor(name);
|
|
||||||
|
|
||||||
let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names);
|
|
||||||
|
|
||||||
// Try multiple names in some cases where the name
|
|
||||||
// differs on the desktop environments or themes.
|
|
||||||
//
|
|
||||||
// Try the better looking (or more suiting) names first.
|
|
||||||
match cursor {
|
|
||||||
CursorIcon::Alias => load(b"link\0"),
|
|
||||||
CursorIcon::Arrow => load(b"arrow\0"),
|
|
||||||
CursorIcon::Cell => load(b"plus\0"),
|
|
||||||
CursorIcon::Copy => load(b"copy\0"),
|
|
||||||
CursorIcon::Crosshair => load(b"crosshair\0"),
|
|
||||||
CursorIcon::Default => load(b"left_ptr\0"),
|
|
||||||
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
|
|
||||||
CursorIcon::Help => load(b"question_arrow\0"),
|
|
||||||
CursorIcon::Move => load(b"move\0"),
|
|
||||||
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
|
|
||||||
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
|
|
||||||
CursorIcon::Progress => load(b"left_ptr_watch\0"),
|
|
||||||
CursorIcon::AllScroll => load(b"all-scroll\0"),
|
|
||||||
CursorIcon::ContextMenu => load(b"context-menu\0"),
|
|
||||||
|
|
||||||
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
|
|
||||||
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
|
|
||||||
|
|
||||||
// Resize cursors
|
|
||||||
CursorIcon::EResize => load(b"right_side\0"),
|
|
||||||
CursorIcon::NResize => load(b"top_side\0"),
|
|
||||||
CursorIcon::NeResize => load(b"top_right_corner\0"),
|
|
||||||
CursorIcon::NwResize => load(b"top_left_corner\0"),
|
|
||||||
CursorIcon::SResize => load(b"bottom_side\0"),
|
|
||||||
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
|
|
||||||
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
|
|
||||||
CursorIcon::WResize => load(b"left_side\0"),
|
|
||||||
CursorIcon::EwResize => load(b"h_double_arrow\0"),
|
|
||||||
CursorIcon::NsResize => load(b"v_double_arrow\0"),
|
|
||||||
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
|
|
||||||
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
|
|
||||||
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
|
|
||||||
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
|
|
||||||
|
|
||||||
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
|
|
||||||
CursorIcon::VerticalText => load(b"vertical-text\0"),
|
|
||||||
|
|
||||||
CursorIcon::Wait => load(b"watch\0"),
|
|
||||||
|
|
||||||
CursorIcon::ZoomIn => load(b"zoom-in\0"),
|
|
||||||
CursorIcon::ZoomOut => load(b"zoom-out\0"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_cursor(&self, cursor: ffi::Cursor) {
|
|
||||||
unsafe {
|
|
||||||
(self.xconn.xlib.XDefineCursor)(self.xconn.display, self.xwindow, cursor);
|
|
||||||
if cursor != 0 {
|
|
||||||
(self.xconn.xlib.XFreeCursor)(self.xconn.display, cursor);
|
|
||||||
}
|
|
||||||
self.xconn
|
|
||||||
.flush_requests()
|
|
||||||
.expect("Failed to set or free the cursor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||||
*self.cursor.lock() = cursor;
|
let old_cursor = replace(&mut *self.cursor.lock(), cursor);
|
||||||
if *self.cursor_visible.lock() {
|
if cursor != old_cursor && *self.cursor_visible.lock() {
|
||||||
self.update_cursor(self.get_cursor(cursor));
|
self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This could maybe be cached. I don't think it's worth
|
|
||||||
// the complexity, since cursor changes are not so common,
|
|
||||||
// and this is just allocating a 1x1 pixmap...
|
|
||||||
fn create_empty_cursor(&self) -> Option<ffi::Cursor> {
|
|
||||||
let data = 0;
|
|
||||||
let pixmap = unsafe {
|
|
||||||
(self.xconn.xlib.XCreateBitmapFromData)(self.xconn.display, self.xwindow, &data, 1, 1)
|
|
||||||
};
|
|
||||||
if pixmap == 0 {
|
|
||||||
// Failed to allocate
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cursor = unsafe {
|
|
||||||
// We don't care about this color, since it only fills bytes
|
|
||||||
// in the pixmap which are not 0 in the mask.
|
|
||||||
let dummy_color: ffi::XColor = mem::uninitialized();
|
|
||||||
let cursor = (self.xconn.xlib.XCreatePixmapCursor)(
|
|
||||||
self.xconn.display,
|
|
||||||
pixmap,
|
|
||||||
pixmap,
|
|
||||||
&dummy_color as *const _ as *mut _,
|
|
||||||
&dummy_color as *const _ as *mut _,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
(self.xconn.xlib.XFreePixmap)(self.xconn.display, pixmap);
|
|
||||||
cursor
|
|
||||||
};
|
|
||||||
Some(cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||||
let mut grabbed_lock = self.cursor_grabbed.lock();
|
let mut grabbed_lock = self.cursor_grabbed.lock();
|
||||||
|
@ -1219,14 +1237,13 @@ impl UnownedWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let cursor = if visible {
|
let cursor = if visible {
|
||||||
self.get_cursor(*self.cursor.lock())
|
Some(*self.cursor.lock())
|
||||||
} else {
|
} else {
|
||||||
self.create_empty_cursor()
|
None
|
||||||
.expect("Failed to create empty cursor")
|
|
||||||
};
|
};
|
||||||
*visible_lock = visible;
|
*visible_lock = visible;
|
||||||
drop(visible_lock);
|
drop(visible_lock);
|
||||||
self.update_cursor(cursor);
|
self.xconn.set_cursor_icon(self.xwindow, cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -1277,4 +1294,13 @@ impl UnownedWindow {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.insert(WindowId(self.xwindow));
|
.insert(WindowId(self.xwindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_window_handle(&self) -> X11Handle {
|
||||||
|
X11Handle {
|
||||||
|
window: self.xwindow,
|
||||||
|
display: self.xconn.display as _,
|
||||||
|
..X11Handle::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use std::{error::Error, fmt, os::raw::c_int, ptr};
|
use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr};
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
use crate::window::CursorIcon;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
|
|
||||||
/// A connection to an X server.
|
/// A connection to an X server.
|
||||||
|
@ -19,6 +21,7 @@ pub struct XConnection {
|
||||||
pub display: *mut ffi::Display,
|
pub display: *mut ffi::Display,
|
||||||
pub x11_fd: c_int,
|
pub x11_fd: c_int,
|
||||||
pub latest_error: Mutex<Option<XError>>,
|
pub latest_error: Mutex<Option<XError>>,
|
||||||
|
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for XConnection {}
|
unsafe impl Send for XConnection {}
|
||||||
|
@ -64,6 +67,7 @@ impl XConnection {
|
||||||
display,
|
display,
|
||||||
x11_fd: fd,
|
x11_fd: fd,
|
||||||
latest_error: Mutex::new(None),
|
latest_error: Mutex::new(None),
|
||||||
|
cursor_cache: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::{
|
||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
hint::unreachable_unchecked,
|
hint::unreachable_unchecked,
|
||||||
mem,
|
mem,
|
||||||
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Mutex, MutexGuard,
|
Mutex, MutexGuard,
|
||||||
|
@ -37,13 +38,13 @@ pub trait EventHandler: Debug {
|
||||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EventLoopHandler<F, T: 'static> {
|
struct EventLoopHandler<T: 'static> {
|
||||||
callback: F,
|
callback: Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
||||||
will_exit: bool,
|
will_exit: bool,
|
||||||
window_target: RootWindowTarget<T>,
|
window_target: Rc<RootWindowTarget<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T> Debug for EventLoopHandler<F, T> {
|
impl<T> Debug for EventLoopHandler<T> {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
formatter
|
formatter
|
||||||
.debug_struct("EventLoopHandler")
|
.debug_struct("EventLoopHandler")
|
||||||
|
@ -52,11 +53,7 @@ impl<F, T> Debug for EventLoopHandler<F, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T> EventHandler for EventLoopHandler<F, T>
|
impl<T> EventHandler for EventLoopHandler<T> {
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
|
|
||||||
T: 'static,
|
|
||||||
{
|
|
||||||
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
|
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
|
||||||
(self.callback)(event.userify(), &self.window_target, control_flow);
|
(self.callback)(event.userify(), &self.window_target, control_flow);
|
||||||
self.will_exit |= *control_flow == ControlFlow::Exit;
|
self.will_exit |= *control_flow == ControlFlow::Exit;
|
||||||
|
@ -87,7 +84,6 @@ struct Handler {
|
||||||
start_time: Mutex<Option<Instant>>,
|
start_time: Mutex<Option<Instant>>,
|
||||||
callback: Mutex<Option<Box<dyn EventHandler>>>,
|
callback: Mutex<Option<Box<dyn EventHandler>>>,
|
||||||
pending_events: Mutex<VecDeque<Event<Never>>>,
|
pending_events: Mutex<VecDeque<Event<Never>>>,
|
||||||
deferred_events: Mutex<VecDeque<Event<Never>>>,
|
|
||||||
pending_redraw: Mutex<Vec<WindowId>>,
|
pending_redraw: Mutex<Vec<WindowId>>,
|
||||||
waker: Mutex<EventLoopWaker>,
|
waker: Mutex<EventLoopWaker>,
|
||||||
}
|
}
|
||||||
|
@ -100,10 +96,6 @@ impl Handler {
|
||||||
self.pending_events.lock().unwrap()
|
self.pending_events.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque<Event<Never>>> {
|
|
||||||
self.deferred_events.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
|
fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
|
||||||
self.pending_redraw.lock().unwrap()
|
self.pending_redraw.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
@ -148,10 +140,6 @@ impl Handler {
|
||||||
mem::replace(&mut *self.events(), Default::default())
|
mem::replace(&mut *self.events(), Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_deferred(&self) -> VecDeque<Event<Never>> {
|
|
||||||
mem::replace(&mut *self.deferred(), Default::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_redraw(&self) -> Vec<WindowId> {
|
fn should_redraw(&self) -> Vec<WindowId> {
|
||||||
mem::replace(&mut *self.redraw(), Default::default())
|
mem::replace(&mut *self.redraw(), Default::default())
|
||||||
}
|
}
|
||||||
|
@ -180,13 +168,20 @@ impl Handler {
|
||||||
pub enum AppState {}
|
pub enum AppState {}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn set_callback<F, T>(callback: F, window_target: RootWindowTarget<T>)
|
// This function extends lifetime of `callback` to 'static as its side effect
|
||||||
|
pub unsafe fn set_callback<F, T>(callback: F, window_target: Rc<RootWindowTarget<T>>)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
|
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
|
||||||
T: 'static,
|
|
||||||
{
|
{
|
||||||
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
||||||
callback,
|
// This transmute is always safe, in case it was reached through `run`, since our
|
||||||
|
// lifetime will be already 'static. In other cases caller should ensure that all data
|
||||||
|
// they passed to callback will actually outlive it, some apps just can't move
|
||||||
|
// everything to event loop, so this is something that they should care about.
|
||||||
|
callback: mem::transmute::<
|
||||||
|
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
||||||
|
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
||||||
|
>(Box::new(callback)),
|
||||||
will_exit: false,
|
will_exit: false,
|
||||||
window_target,
|
window_target,
|
||||||
}));
|
}));
|
||||||
|
@ -196,6 +191,7 @@ impl AppState {
|
||||||
HANDLER.set_in_callback(true);
|
HANDLER.set_in_callback(true);
|
||||||
HANDLER.handle_nonuser_event(Event::LoopDestroyed);
|
HANDLER.handle_nonuser_event(Event::LoopDestroyed);
|
||||||
HANDLER.set_in_callback(false);
|
HANDLER.set_in_callback(false);
|
||||||
|
HANDLER.callback.lock().unwrap().take();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launched() {
|
pub fn launched() {
|
||||||
|
@ -259,20 +255,6 @@ impl AppState {
|
||||||
HANDLER.events().append(&mut events);
|
HANDLER.events().append(&mut events);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_event_immediately(event: Event<Never>) {
|
|
||||||
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
|
|
||||||
panic!("Event sent from different thread: {:#?}", event);
|
|
||||||
}
|
|
||||||
HANDLER.deferred().push_back(event);
|
|
||||||
if !HANDLER.get_in_callback() {
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
for event in HANDLER.take_deferred() {
|
|
||||||
HANDLER.handle_nonuser_event(event);
|
|
||||||
}
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cleared() {
|
pub fn cleared() {
|
||||||
if !HANDLER.is_ready() {
|
if !HANDLER.is_ready() {
|
||||||
return;
|
return;
|
||||||
|
@ -298,7 +280,7 @@ impl AppState {
|
||||||
}
|
}
|
||||||
HANDLER.update_start_time();
|
HANDLER.update_start_time();
|
||||||
match HANDLER.get_old_and_new_control_flow() {
|
match HANDLER.get_old_and_new_control_flow() {
|
||||||
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(),
|
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (),
|
||||||
(old, new) if old == new => (),
|
(old, new) if old == new => (),
|
||||||
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
|
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
|
||||||
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),
|
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, sync::mpsc,
|
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, rc::Rc,
|
||||||
|
sync::mpsc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -34,7 +35,7 @@ impl<T> Default for EventLoopWindowTarget<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
pub struct EventLoop<T: 'static> {
|
||||||
window_target: RootWindowTarget<T>,
|
window_target: Rc<RootWindowTarget<T>>,
|
||||||
_delegate: IdRef,
|
_delegate: IdRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +60,10 @@ impl<T> EventLoop<T> {
|
||||||
};
|
};
|
||||||
setup_control_flow_observers();
|
setup_control_flow_observers();
|
||||||
EventLoop {
|
EventLoop {
|
||||||
window_target: RootWindowTarget {
|
window_target: Rc::new(RootWindowTarget {
|
||||||
p: Default::default(),
|
p: Default::default(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
},
|
}),
|
||||||
_delegate: delegate,
|
_delegate: delegate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,46 +82,50 @@ impl<T> EventLoop<T> {
|
||||||
&self.window_target
|
&self.window_target
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run<F>(self, callback: F) -> !
|
pub fn run<F>(mut self, callback: F) -> !
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
|
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
|
||||||
|
{
|
||||||
|
self.run_return(callback);
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_return<F>(&mut self, callback: F)
|
||||||
|
where
|
||||||
|
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let _pool = NSAutoreleasePool::new(nil);
|
let _pool = NSAutoreleasePool::new(nil);
|
||||||
let app = NSApp();
|
let app = NSApp();
|
||||||
assert_ne!(app, nil);
|
assert_ne!(app, nil);
|
||||||
AppState::set_callback(callback, self.window_target);
|
AppState::set_callback(callback, Rc::clone(&self.window_target));
|
||||||
let _: () = msg_send![app, run];
|
let _: () = msg_send![app, run];
|
||||||
AppState::exit();
|
AppState::exit();
|
||||||
process::exit(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, _callback: F)
|
|
||||||
where
|
|
||||||
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> Proxy<T> {
|
pub fn create_proxy(&self) -> Proxy<T> {
|
||||||
Proxy::new(self.window_target.p.sender.clone())
|
Proxy::new(self.window_target.p.sender.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Proxy<T> {
|
pub struct Proxy<T> {
|
||||||
sender: mpsc::Sender<T>,
|
sender: mpsc::Sender<T>,
|
||||||
source: CFRunLoopSourceRef,
|
source: CFRunLoopSourceRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T> Send for Proxy<T> {}
|
unsafe impl<T: Send> Send for Proxy<T> {}
|
||||||
unsafe impl<T> Sync for Proxy<T> {}
|
|
||||||
|
impl<T> Clone for Proxy<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Proxy::new(self.sender.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Proxy<T> {
|
impl<T> Proxy<T> {
|
||||||
fn new(sender: mpsc::Sender<T>) -> Self {
|
fn new(sender: mpsc::Sender<T>) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
// just wakeup the eventloop
|
// just wake up the eventloop
|
||||||
extern "C" fn event_loop_proxy_handler(_: *mut 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
|
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||||
|
|
|
@ -6,6 +6,13 @@ use cocoa::{
|
||||||
base::id,
|
base::id,
|
||||||
foundation::{NSInteger, NSUInteger},
|
foundation::{NSInteger, NSUInteger},
|
||||||
};
|
};
|
||||||
|
use core_foundation::{
|
||||||
|
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
|
||||||
|
};
|
||||||
|
use core_graphics::{
|
||||||
|
base::CGError,
|
||||||
|
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
||||||
|
};
|
||||||
use objc;
|
use objc;
|
||||||
|
|
||||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
||||||
|
@ -108,3 +115,95 @@ pub enum NSWindowLevel {
|
||||||
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
||||||
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type CGDisplayFadeInterval = f32;
|
||||||
|
pub type CGDisplayReservationInterval = f32;
|
||||||
|
pub type CGDisplayBlendFraction = f32;
|
||||||
|
|
||||||
|
pub const kCGDisplayBlendNormal: f32 = 0.0;
|
||||||
|
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
||||||
|
|
||||||
|
pub type CGDisplayFadeReservationToken = u32;
|
||||||
|
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
|
||||||
|
|
||||||
|
pub type Boolean = u8;
|
||||||
|
pub const FALSE: Boolean = 0;
|
||||||
|
pub const TRUE: Boolean = 1;
|
||||||
|
|
||||||
|
pub const kCGErrorSuccess: i32 = 0;
|
||||||
|
pub const kCGErrorFailure: i32 = 1000;
|
||||||
|
pub const kCGErrorIllegalArgument: i32 = 1001;
|
||||||
|
pub const kCGErrorInvalidConnection: i32 = 1002;
|
||||||
|
pub const kCGErrorInvalidContext: i32 = 1003;
|
||||||
|
pub const kCGErrorCannotComplete: i32 = 1004;
|
||||||
|
pub const kCGErrorNotImplemented: i32 = 1006;
|
||||||
|
pub const kCGErrorRangeCheck: i32 = 1007;
|
||||||
|
pub const kCGErrorTypeCheck: i32 = 1008;
|
||||||
|
pub const kCGErrorInvalidOperation: i32 = 1010;
|
||||||
|
pub const kCGErrorNoneAvailable: i32 = 1011;
|
||||||
|
|
||||||
|
pub const IO1BitIndexedPixels: &str = "P";
|
||||||
|
pub const IO2BitIndexedPixels: &str = "PP";
|
||||||
|
pub const IO4BitIndexedPixels: &str = "PPPP";
|
||||||
|
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
|
||||||
|
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
|
||||||
|
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
|
||||||
|
|
||||||
|
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
|
||||||
|
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
|
||||||
|
|
||||||
|
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
|
||||||
|
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
|
||||||
|
|
||||||
|
pub const IOYUV422Pixels: &str = "Y4U2V2";
|
||||||
|
pub const IO8BitOverlayPixels: &str = "O8";
|
||||||
|
|
||||||
|
pub type CGWindowLevel = i32;
|
||||||
|
pub type CGDisplayModeRef = *mut libc::c_void;
|
||||||
|
|
||||||
|
#[link(name = "CoreGraphics", kind = "framework")]
|
||||||
|
extern "C" {
|
||||||
|
pub fn CGRestorePermanentDisplayConfiguration();
|
||||||
|
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
|
||||||
|
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
|
||||||
|
pub fn CGConfigureDisplayFadeEffect(
|
||||||
|
config: CGDisplayConfigRef,
|
||||||
|
fadeOutSeconds: CGDisplayFadeInterval,
|
||||||
|
fadeInSeconds: CGDisplayFadeInterval,
|
||||||
|
fadeRed: f32,
|
||||||
|
fadeGreen: f32,
|
||||||
|
fadeBlue: f32,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGAcquireDisplayFadeReservation(
|
||||||
|
seconds: CGDisplayReservationInterval,
|
||||||
|
token: *mut CGDisplayFadeReservationToken,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGDisplayFade(
|
||||||
|
token: CGDisplayFadeReservationToken,
|
||||||
|
duration: CGDisplayFadeInterval,
|
||||||
|
startBlend: CGDisplayBlendFraction,
|
||||||
|
endBlend: CGDisplayBlendFraction,
|
||||||
|
redBlend: f32,
|
||||||
|
greenBlend: f32,
|
||||||
|
blueBlend: f32,
|
||||||
|
synchronous: Boolean,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
|
||||||
|
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||||
|
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
|
||||||
|
pub fn CGDisplaySetDisplayMode(
|
||||||
|
display: CGDirectDisplayID,
|
||||||
|
mode: CGDisplayModeRef,
|
||||||
|
options: CFDictionaryRef,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGDisplayCopyAllDisplayModes(
|
||||||
|
display: CGDirectDisplayID,
|
||||||
|
options: CFDictionaryRef,
|
||||||
|
) -> CFArrayRef;
|
||||||
|
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
|
||||||
|
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
|
||||||
|
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
|
||||||
|
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
|
||||||
|
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
|
||||||
|
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::{fmt, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -1,25 +1,119 @@
|
||||||
use std::{collections::VecDeque, fmt};
|
use std::{collections::VecDeque, fmt};
|
||||||
|
|
||||||
|
use super::ffi;
|
||||||
|
use crate::{
|
||||||
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform_impl::platform::util::IdRef,
|
||||||
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::NSScreen,
|
appkit::NSScreen,
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSString, NSUInteger},
|
foundation::{NSString, NSUInteger},
|
||||||
};
|
};
|
||||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayMode};
|
use core_foundation::{
|
||||||
|
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
|
||||||
|
base::{CFRelease, TCFType},
|
||||||
|
string::CFString,
|
||||||
|
};
|
||||||
|
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||||
use core_video_sys::{
|
use core_video_sys::{
|
||||||
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
|
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
|
||||||
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
|
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
#[derive(Derivative)]
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
#[derivative(Debug, Clone, PartialEq, Hash)]
|
||||||
monitor::VideoMode,
|
pub struct VideoMode {
|
||||||
platform_impl::platform::util::IdRef,
|
pub(crate) size: (u32, u32),
|
||||||
};
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) monitor: MonitorHandle,
|
||||||
|
#[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
|
||||||
|
pub(crate) native_mode: NativeDisplayMode,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
|
||||||
|
|
||||||
|
unsafe impl Send for NativeDisplayMode {}
|
||||||
|
|
||||||
|
impl Drop for NativeDisplayMode {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayModeRelease(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for NativeDisplayMode {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayModeRetain(self.0);
|
||||||
|
}
|
||||||
|
NativeDisplayMode(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
self.bit_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
self.refresh_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
RootMonitorHandle {
|
||||||
|
inner: self.monitor.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct MonitorHandle(CGDirectDisplayID);
|
pub struct MonitorHandle(CGDirectDisplayID);
|
||||||
|
|
||||||
|
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
|
||||||
|
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
|
||||||
|
// unique identifier that persists even across system reboots
|
||||||
|
impl PartialEq for MonitorHandle {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||||
|
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for MonitorHandle {}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||||
|
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for MonitorHandle {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||||
if let Ok(displays) = CGDisplay::active_displays() {
|
if let Ok(displays) = CGDisplay::active_displays() {
|
||||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||||
|
@ -101,7 +195,7 @@ impl MonitorHandle {
|
||||||
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
let cv_refresh_rate = unsafe {
|
let cv_refresh_rate = unsafe {
|
||||||
let mut display_link = std::ptr::null_mut();
|
let mut display_link = std::ptr::null_mut();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -117,11 +211,27 @@ impl MonitorHandle {
|
||||||
time.timeScale as i64 / time.timeValue
|
time.timeScale as i64 / time.timeValue
|
||||||
};
|
};
|
||||||
|
|
||||||
CGDisplayMode::all_display_modes(self.0, std::ptr::null())
|
let monitor = self.clone();
|
||||||
.expect("failed to obtain list of display modes")
|
|
||||||
.into_iter()
|
unsafe {
|
||||||
.map(move |mode| {
|
let modes = {
|
||||||
let cg_refresh_rate = mode.refresh_rate().round() as i64;
|
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
|
||||||
|
assert!(!array.is_null(), "failed to get list of display modes");
|
||||||
|
let array_count = CFArrayGetCount(array);
|
||||||
|
let modes: Vec<_> = (0..array_count)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |i| {
|
||||||
|
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
||||||
|
ffi::CGDisplayModeRetain(mode);
|
||||||
|
mode
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
CFRelease(array as *const _);
|
||||||
|
modes
|
||||||
|
};
|
||||||
|
|
||||||
|
modes.into_iter().map(move |mode| {
|
||||||
|
let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
|
||||||
|
|
||||||
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
||||||
// isn't a CRT
|
// isn't a CRT
|
||||||
|
@ -131,34 +241,55 @@ impl MonitorHandle {
|
||||||
cv_refresh_rate
|
cv_refresh_rate
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoMode {
|
let pixel_encoding =
|
||||||
size: (mode.width() as u32, mode.height() as u32),
|
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode))
|
||||||
|
.to_string();
|
||||||
|
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
|
||||||
|
32
|
||||||
|
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
|
||||||
|
16
|
||||||
|
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
|
||||||
|
30
|
||||||
|
} else {
|
||||||
|
unimplemented!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let video_mode = VideoMode {
|
||||||
|
size: (
|
||||||
|
ffi::CGDisplayModeGetPixelWidth(mode) as u32,
|
||||||
|
ffi::CGDisplayModeGetPixelHeight(mode) as u32,
|
||||||
|
),
|
||||||
refresh_rate: refresh_rate as u16,
|
refresh_rate: refresh_rate as u16,
|
||||||
bit_depth: mode.bit_depth() as u16,
|
bit_depth,
|
||||||
}
|
monitor: monitor.clone(),
|
||||||
|
native_mode: NativeDisplayMode(mode),
|
||||||
|
};
|
||||||
|
|
||||||
|
RootVideoMode { video_mode }
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ns_screen(&self) -> Option<id> {
|
pub(crate) fn ns_screen(&self) -> Option<id> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let native_id = self.native_identifier();
|
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
|
||||||
let screens = NSScreen::screens(nil);
|
let screens = NSScreen::screens(nil);
|
||||||
let count: NSUInteger = msg_send![screens, count];
|
let count: NSUInteger = msg_send![screens, count];
|
||||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||||
let mut matching_screen: Option<id> = None;
|
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||||
let device_description = NSScreen::deviceDescription(screen);
|
let device_description = NSScreen::deviceDescription(screen);
|
||||||
let value: id = msg_send![device_description, objectForKey:*key];
|
let value: id = msg_send![device_description, objectForKey:*key];
|
||||||
if value != nil {
|
if value != nil {
|
||||||
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue];
|
let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
|
||||||
if screen_number as u32 == native_id {
|
let other_uuid =
|
||||||
matching_screen = Some(screen);
|
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
|
||||||
break;
|
if uuid == other_uuid {
|
||||||
|
return Some(screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matching_screen
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::{self, os::raw::*, ptr, time::Instant};
|
use std::{self, os::raw::*, ptr, time::Instant};
|
||||||
|
|
||||||
use crate::platform_impl::platform::app_state::AppState;
|
use crate::platform_impl::platform::{app_state::AppState, ffi};
|
||||||
|
|
||||||
#[link(name = "CoreFoundation", kind = "framework")]
|
#[link(name = "CoreFoundation", kind = "framework")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
|
|
||||||
pub static kCFRunLoopCommonModes: CFRunLoopMode;
|
pub static kCFRunLoopCommonModes: CFRunLoopMode;
|
||||||
|
|
||||||
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
|
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
|
||||||
|
@ -13,7 +12,7 @@ extern "C" {
|
||||||
pub fn CFRunLoopObserverCreate(
|
pub fn CFRunLoopObserverCreate(
|
||||||
allocator: CFAllocatorRef,
|
allocator: CFAllocatorRef,
|
||||||
activities: CFOptionFlags,
|
activities: CFOptionFlags,
|
||||||
repeats: Boolean,
|
repeats: ffi::Boolean,
|
||||||
order: CFIndex,
|
order: CFIndex,
|
||||||
callout: CFRunLoopObserverCallBack,
|
callout: CFRunLoopObserverCallBack,
|
||||||
context: *mut CFRunLoopObserverContext,
|
context: *mut CFRunLoopObserverContext,
|
||||||
|
@ -51,11 +50,6 @@ extern "C" {
|
||||||
pub fn CFRelease(cftype: *const c_void);
|
pub fn CFRelease(cftype: *const c_void);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Boolean = u8;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const FALSE: Boolean = 0;
|
|
||||||
const TRUE: Boolean = 1;
|
|
||||||
|
|
||||||
pub enum CFAllocator {}
|
pub enum CFAllocator {}
|
||||||
pub type CFAllocatorRef = *mut CFAllocator;
|
pub type CFAllocatorRef = *mut CFAllocator;
|
||||||
pub enum CFRunLoop {}
|
pub enum CFRunLoop {}
|
||||||
|
@ -102,7 +96,7 @@ pub struct CFRunLoopSourceContext {
|
||||||
pub retain: extern "C" fn(*const c_void) -> *const c_void,
|
pub retain: extern "C" fn(*const c_void) -> *const c_void,
|
||||||
pub release: extern "C" fn(*const c_void),
|
pub release: extern "C" fn(*const c_void),
|
||||||
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
|
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
|
||||||
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
|
pub equal: extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean,
|
||||||
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
|
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
|
||||||
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||||
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||||
|
@ -162,12 +156,12 @@ impl RunLoop {
|
||||||
let observer = CFRunLoopObserverCreate(
|
let observer = CFRunLoopObserverCreate(
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
flags,
|
flags,
|
||||||
TRUE, // Indicates we want this to run repeatedly
|
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||||
priority, // The lower the value, the sooner this will run
|
priority, // The lower the value, the sooner this will run
|
||||||
handler,
|
handler,
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
);
|
);
|
||||||
CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode);
|
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,9 +198,9 @@ impl Default for EventLoopWaker {
|
||||||
fn default() -> EventLoopWaker {
|
fn default() -> EventLoopWaker {
|
||||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||||
unsafe {
|
unsafe {
|
||||||
// create a timer with a 1µs interval (1ns does not work) to mimic polling.
|
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
||||||
// it is initially setup with a first fire time really far into the
|
// It is initially setup with a first fire time really far into the
|
||||||
// future, but that gets changed to fire immediatley in did_finish_launching
|
// future, but that gets changed to fire immediately in did_finish_launching
|
||||||
let timer = CFRunLoopTimerCreate(
|
let timer = CFRunLoopTimerCreate(
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
std::f64::MAX,
|
std::f64::MAX,
|
||||||
|
|
|
@ -206,7 +206,10 @@ extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Window level must be restored from `CGShieldingWindowLevel()
|
||||||
|
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||||
|
// anything
|
||||||
|
context.ns_window.setLevel_(0);
|
||||||
context.ns_window.toggleFullScreen_(nil);
|
context.ns_window.toggleFullScreen_(nil);
|
||||||
}
|
}
|
||||||
Box::from_raw(context_ptr);
|
Box::from_raw(context_ptr);
|
||||||
|
@ -228,6 +231,21 @@ pub unsafe fn toggle_full_screen_async(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn restore_display_mode_callback(screen: *mut c_void) {
|
||||||
|
unsafe {
|
||||||
|
let screen = Box::from_raw(screen as *mut u32);
|
||||||
|
ffi::CGRestorePermanentDisplayConfiguration();
|
||||||
|
assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
||||||
|
dispatch_async_f(
|
||||||
|
dispatch_get_main_queue(),
|
||||||
|
Box::into_raw(Box::new(ns_screen)) as *mut _,
|
||||||
|
restore_display_mode_callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
struct SetMaximizedData {
|
struct SetMaximizedData {
|
||||||
ns_window: id,
|
ns_window: id,
|
||||||
is_zoomed: bool,
|
is_zoomed: bool,
|
||||||
|
|
|
@ -15,10 +15,12 @@ pub enum Cursor {
|
||||||
|
|
||||||
impl From<CursorIcon> for Cursor {
|
impl From<CursorIcon> for Cursor {
|
||||||
fn from(cursor: CursorIcon) -> Self {
|
fn from(cursor: CursorIcon) -> Self {
|
||||||
|
// See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc.
|
||||||
match cursor {
|
match cursor {
|
||||||
CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"),
|
CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"),
|
||||||
CursorIcon::Hand => Cursor::Native("pointingHandCursor"),
|
CursorIcon::Hand => Cursor::Native("pointingHandCursor"),
|
||||||
CursorIcon::Grabbing | CursorIcon::Grab => Cursor::Native("closedHandCursor"),
|
CursorIcon::Grab => Cursor::Native("openHandCursor"),
|
||||||
|
CursorIcon::Grabbing => Cursor::Native("closedHandCursor"),
|
||||||
CursorIcon::Text => Cursor::Native("IBeamCursor"),
|
CursorIcon::Text => Cursor::Native("IBeamCursor"),
|
||||||
CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
|
CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
|
||||||
CursorIcon::Copy => Cursor::Native("dragCopyCursor"),
|
CursorIcon::Copy => Cursor::Native("dragCopyCursor"),
|
||||||
|
|
|
@ -104,7 +104,7 @@ lazy_static! {
|
||||||
);
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(drawRect:),
|
sel!(drawRect:),
|
||||||
draw_rect as extern "C" fn(&Object, Sel, id),
|
draw_rect as extern "C" fn(&Object, Sel, NSRect),
|
||||||
);
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(acceptsFirstResponder),
|
sel!(acceptsFirstResponder),
|
||||||
|
@ -280,7 +280,7 @@ extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) {
|
||||||
trace!("Completed `viewDidMoveToWindow`");
|
trace!("Completed `viewDidMoveToWindow`");
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: id) {
|
extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||||
let state = &mut *(state_ptr as *mut ViewState);
|
let state = &mut *(state_ptr as *mut ViewState);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use raw_window_handle::{macos::MacOSHandle, RawWindowHandle};
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
f64,
|
f64,
|
||||||
|
@ -8,6 +9,23 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
|
icon::Icon,
|
||||||
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
|
||||||
|
platform_impl::platform::{
|
||||||
|
app_state::AppState,
|
||||||
|
ffi,
|
||||||
|
monitor::{self, MonitorHandle, VideoMode},
|
||||||
|
util::{self, IdRef},
|
||||||
|
view::{self, new_view},
|
||||||
|
window_delegate::new_delegate,
|
||||||
|
OsError,
|
||||||
|
},
|
||||||
|
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||||
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{
|
appkit::{
|
||||||
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
|
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
|
||||||
|
@ -17,30 +35,12 @@ use cocoa::{
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||||
};
|
};
|
||||||
use core_graphics::display::CGDisplay;
|
use core_graphics::display::{CGDisplay, CGDisplayMode};
|
||||||
use objc::{
|
use objc::{
|
||||||
declare::ClassDecl,
|
declare::ClassDecl,
|
||||||
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
|
||||||
icon::Icon,
|
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
|
||||||
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
|
|
||||||
platform_impl::platform::{
|
|
||||||
app_state::AppState,
|
|
||||||
ffi,
|
|
||||||
monitor::{self, MonitorHandle},
|
|
||||||
util::{self, IdRef},
|
|
||||||
view::{self, new_view},
|
|
||||||
window_delegate::new_delegate,
|
|
||||||
OsError,
|
|
||||||
},
|
|
||||||
window::{CursorIcon, WindowAttributes, WindowId as RootWindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Id(pub usize);
|
pub struct Id(pub usize);
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub titlebar_buttons_hidden: bool,
|
pub titlebar_buttons_hidden: bool,
|
||||||
pub fullsize_content_view: bool,
|
pub fullsize_content_view: bool,
|
||||||
pub resize_increments: Option<LogicalSize>,
|
pub resize_increments: Option<LogicalSize>,
|
||||||
|
pub disallow_hidpi: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
|
fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
|
||||||
|
@ -86,10 +87,15 @@ fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn create_view(ns_window: id) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
|
unsafe fn create_view(
|
||||||
|
ns_window: id,
|
||||||
|
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
|
||||||
|
) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
|
||||||
let (ns_view, cursor) = new_view(ns_window);
|
let (ns_view, cursor) = new_view(ns_window);
|
||||||
ns_view.non_nil().map(|ns_view| {
|
ns_view.non_nil().map(|ns_view| {
|
||||||
ns_view.setWantsBestResolutionOpenGLSurface_(YES);
|
if !pl_attribs.disallow_hidpi {
|
||||||
|
ns_view.setWantsBestResolutionOpenGLSurface_(YES);
|
||||||
|
}
|
||||||
|
|
||||||
// On Mojave, views automatically become layer-backed shortly after being added to
|
// On Mojave, views automatically become layer-backed shortly after being added to
|
||||||
// a window. Changing the layer-backedness of a view breaks the association between
|
// a window. Changing the layer-backedness of a view breaks the association between
|
||||||
|
@ -113,11 +119,14 @@ fn create_window(
|
||||||
unsafe {
|
unsafe {
|
||||||
let pool = NSAutoreleasePool::new(nil);
|
let pool = NSAutoreleasePool::new(nil);
|
||||||
let screen = match attrs.fullscreen {
|
let screen = match attrs.fullscreen {
|
||||||
Some(ref monitor_id) => {
|
Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }))
|
||||||
let monitor_screen = monitor_id.inner.ns_screen();
|
| Some(Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: VideoMode { ref monitor, .. },
|
||||||
|
})) => {
|
||||||
|
let monitor_screen = monitor.ns_screen();
|
||||||
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
|
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
|
||||||
}
|
}
|
||||||
_ => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let frame = match screen {
|
let frame = match screen {
|
||||||
Some(screen) => appkit::NSScreen::frame(screen),
|
Some(screen) => appkit::NSScreen::frame(screen),
|
||||||
|
@ -233,12 +242,15 @@ lazy_static! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
pub resizable: bool,
|
pub resizable: bool,
|
||||||
pub fullscreen: Option<RootMonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
pub maximized: bool,
|
pub maximized: bool,
|
||||||
pub standard_frame: Option<NSRect>,
|
pub standard_frame: Option<NSRect>,
|
||||||
is_simple_fullscreen: bool,
|
is_simple_fullscreen: bool,
|
||||||
pub saved_style: Option<NSWindowStyleMask>,
|
pub saved_style: Option<NSWindowStyleMask>,
|
||||||
|
/// Presentation options saved before entering `set_simple_fullscreen`, and
|
||||||
|
/// restored upon exiting it
|
||||||
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
||||||
|
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
|
@ -301,10 +313,11 @@ impl UnownedWindow {
|
||||||
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
|
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (ns_view, cursor) = unsafe { create_view(*ns_window) }.ok_or_else(|| {
|
let (ns_view, cursor) =
|
||||||
unsafe { pool.drain() };
|
unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
|
||||||
os_error!(OsError::CreationError("Couldn't create `NSView`"))
|
unsafe { pool.drain() };
|
||||||
})?;
|
os_error!(OsError::CreationError("Couldn't create `NSView`"))
|
||||||
|
})?;
|
||||||
|
|
||||||
let input_context = unsafe { util::create_input_context(*ns_view) };
|
let input_context = unsafe { util::create_input_context(*ns_view) };
|
||||||
|
|
||||||
|
@ -355,16 +368,7 @@ impl UnownedWindow {
|
||||||
let delegate = new_delegate(&window, fullscreen.is_some());
|
let delegate = new_delegate(&window, fullscreen.is_some());
|
||||||
|
|
||||||
// Set fullscreen mode after we setup everything
|
// Set fullscreen mode after we setup everything
|
||||||
if let Some(monitor) = fullscreen {
|
window.set_fullscreen(fullscreen);
|
||||||
if monitor.inner != window.current_monitor().inner {
|
|
||||||
// To do this with native fullscreen, we probably need to
|
|
||||||
// warp the window... while we could use
|
|
||||||
// `enterFullScreenMode`, they're idiomatically different
|
|
||||||
// fullscreen modes, so we'd have to support both anyway.
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
window.set_fullscreen(Some(monitor));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting the window as key has to happen *after* we set the fullscreen
|
// Setting the window as key has to happen *after* we set the fullscreen
|
||||||
// state, since otherwise we'll briefly see the window at normal size
|
// state, since otherwise we'll briefly see the window at normal size
|
||||||
|
@ -594,19 +598,22 @@ impl UnownedWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is called when the window is exiting fullscreen, whether by the
|
||||||
|
/// user clicking on the green fullscreen button or programmatically by
|
||||||
|
/// `toggleFullScreen:`
|
||||||
pub(crate) fn restore_state_from_fullscreen(&self) {
|
pub(crate) fn restore_state_from_fullscreen(&self) {
|
||||||
let maximized = {
|
trace!("Locked shared state in `restore_state_from_fullscreen`");
|
||||||
trace!("Locked shared state in `restore_state_from_fullscreen`");
|
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
|
||||||
|
|
||||||
shared_state_lock.fullscreen = None;
|
shared_state_lock.fullscreen = None;
|
||||||
|
|
||||||
let mask = self.saved_style(&mut *shared_state_lock);
|
let maximized = shared_state_lock.maximized;
|
||||||
|
let mask = self.saved_style(&mut *shared_state_lock);
|
||||||
|
|
||||||
self.set_style_mask_async(mask);
|
drop(shared_state_lock);
|
||||||
shared_state_lock.maximized
|
|
||||||
};
|
|
||||||
trace!("Unocked shared state in `restore_state_from_fullscreen`");
|
trace!("Unocked shared state in `restore_state_from_fullscreen`");
|
||||||
|
|
||||||
|
self.set_style_mask_async(mask);
|
||||||
self.set_maximized(maximized);
|
self.set_maximized(maximized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,44 +634,168 @@ impl UnownedWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
shared_state_lock.fullscreen.clone()
|
shared_state_lock.fullscreen.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// TODO: Right now set_fullscreen do not work on switching monitors
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
/// in fullscreen mode
|
trace!("Locked shared state in `set_fullscreen`");
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
|
||||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
if shared_state_lock.is_simple_fullscreen {
|
if shared_state_lock.is_simple_fullscreen {
|
||||||
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let old_fullscreen = shared_state_lock.fullscreen.clone();
|
||||||
let not_fullscreen = {
|
if fullscreen == old_fullscreen {
|
||||||
trace!("Locked shared state in `set_fullscreen`");
|
|
||||||
let current = &shared_state_lock.fullscreen;
|
|
||||||
match (current, monitor) {
|
|
||||||
(&Some(ref a), Some(ref b)) if a.inner != b.inner => {
|
|
||||||
// Our best bet is probably to move to the origin of the
|
|
||||||
// target monitor.
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
(&None, None) | (&Some(_), Some(_)) => return,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
trace!("Unlocked shared state in `set_fullscreen`");
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
current.is_none()
|
return;
|
||||||
};
|
}
|
||||||
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
|
drop(shared_state_lock);
|
||||||
|
|
||||||
unsafe {
|
// If the fullscreen is on a different monitor, we must move the window
|
||||||
util::toggle_full_screen_async(
|
// to that monitor before we toggle fullscreen (as `toggleFullScreen`
|
||||||
*self.ns_window,
|
// does not take a screen parameter, but uses the current screen)
|
||||||
*self.ns_view,
|
if let Some(ref fullscreen) = fullscreen {
|
||||||
not_fullscreen,
|
let new_screen = match fullscreen {
|
||||||
Arc::downgrade(&self.shared_state),
|
Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor,
|
||||||
)
|
Fullscreen::Exclusive(RootVideoMode {
|
||||||
};
|
video_mode: VideoMode { ref monitor, .. },
|
||||||
|
}) => monitor,
|
||||||
|
}
|
||||||
|
.ns_screen()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let old_screen = NSWindow::screen(*self.ns_window);
|
||||||
|
if old_screen != new_screen {
|
||||||
|
let mut screen_frame: NSRect = msg_send![new_screen, frame];
|
||||||
|
// The coordinate system here has its origin at bottom-left
|
||||||
|
// and Y goes up
|
||||||
|
screen_frame.origin.y += screen_frame.size.height;
|
||||||
|
util::set_frame_top_left_point_async(*self.ns_window, screen_frame.origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
|
||||||
|
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
|
||||||
|
// same thing as we're doing here (captures the display, sets the
|
||||||
|
// video mode, and hides the menu bar and dock), with the exception
|
||||||
|
// of that I couldn't figure out how to set the display mode with
|
||||||
|
// it. I think `enterFullScreenMode:withOptions:` is still using the
|
||||||
|
// older display mode API where display modes were of the type
|
||||||
|
// `CFDictionary`, but this has changed, so we can't obtain the
|
||||||
|
// correct parameter for this any longer. Apple's code samples for
|
||||||
|
// this function seem to just pass in "YES" for the display mode
|
||||||
|
// parameter, which is not consistent with the docs saying that it
|
||||||
|
// takes a `NSDictionary`..
|
||||||
|
|
||||||
|
let display_id = video_mode.monitor().inner.native_identifier();
|
||||||
|
|
||||||
|
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Fade to black (and wait for the fade to complete) to hide the
|
||||||
|
// flicker from capturing the display and switching display mode
|
||||||
|
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
|
||||||
|
== ffi::kCGErrorSuccess
|
||||||
|
{
|
||||||
|
ffi::CGDisplayFade(
|
||||||
|
fade_token,
|
||||||
|
0.3,
|
||||||
|
ffi::kCGDisplayBlendNormal,
|
||||||
|
ffi::kCGDisplayBlendSolidColor,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
ffi::TRUE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let result = ffi::CGDisplaySetDisplayMode(
|
||||||
|
display_id,
|
||||||
|
video_mode.video_mode.native_mode.0,
|
||||||
|
std::ptr::null(),
|
||||||
|
);
|
||||||
|
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
|
||||||
|
|
||||||
|
// After the display has been configured, fade back in
|
||||||
|
// asynchronously
|
||||||
|
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
|
||||||
|
ffi::CGDisplayFade(
|
||||||
|
fade_token,
|
||||||
|
0.6,
|
||||||
|
ffi::kCGDisplayBlendSolidColor,
|
||||||
|
ffi::kCGDisplayBlendNormal,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
ffi::FALSE,
|
||||||
|
);
|
||||||
|
ffi::CGReleaseDisplayFadeReservation(fade_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("Locked shared state in `set_fullscreen`");
|
||||||
|
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
|
shared_state_lock.fullscreen = fullscreen.clone();
|
||||||
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
|
|
||||||
|
match (&old_fullscreen, &fullscreen) {
|
||||||
|
(&None, &Some(_)) => unsafe {
|
||||||
|
util::toggle_full_screen_async(
|
||||||
|
*self.ns_window,
|
||||||
|
*self.ns_view,
|
||||||
|
old_fullscreen.is_none(),
|
||||||
|
Arc::downgrade(&self.shared_state),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(&Some(Fullscreen::Borderless(_)), &None) => unsafe {
|
||||||
|
// State is restored by `window_did_exit_fullscreen`
|
||||||
|
util::toggle_full_screen_async(
|
||||||
|
*self.ns_window,
|
||||||
|
*self.ns_view,
|
||||||
|
old_fullscreen.is_none(),
|
||||||
|
Arc::downgrade(&self.shared_state),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => unsafe {
|
||||||
|
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
|
||||||
|
// Rest of the state is restored by `window_did_exit_fullscreen`
|
||||||
|
util::toggle_full_screen_async(
|
||||||
|
*self.ns_window,
|
||||||
|
*self.ns_view,
|
||||||
|
old_fullscreen.is_none(),
|
||||||
|
Arc::downgrade(&self.shared_state),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe {
|
||||||
|
// If we're already in fullscreen mode, calling
|
||||||
|
// `CGDisplayCapture` will place the shielding window on top of
|
||||||
|
// our window, which results in a black display and is not what
|
||||||
|
// we want. So, we must place our window on top of the shielding
|
||||||
|
// window. Unfortunately, this also makes our window be on top
|
||||||
|
// of the menu bar, and this looks broken, so we must make sure
|
||||||
|
// that the menu bar is disabled. This is done in the window
|
||||||
|
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
||||||
|
msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1];
|
||||||
|
},
|
||||||
|
(
|
||||||
|
&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })),
|
||||||
|
&Some(Fullscreen::Borderless(_)),
|
||||||
|
) => unsafe {
|
||||||
|
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -764,6 +895,16 @@ impl UnownedWindow {
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||||
monitor::primary_monitor()
|
monitor::primary_monitor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
let handle = MacOSHandle {
|
||||||
|
ns_window: *self.ns_window as *mut _,
|
||||||
|
ns_view: *self.ns_view as *mut _,
|
||||||
|
..MacOSHandle::empty()
|
||||||
|
};
|
||||||
|
RawWindowHandle::MacOS(handle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowExtMacOS for UnownedWindow {
|
impl WindowExtMacOS for UnownedWindow {
|
||||||
|
|
|
@ -5,9 +5,9 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{self, NSView, NSWindow},
|
appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::NSAutoreleasePool,
|
foundation::{NSAutoreleasePool, NSUInteger},
|
||||||
};
|
};
|
||||||
use objc::{
|
use objc::{
|
||||||
declare::ClassDecl,
|
declare::ClassDecl,
|
||||||
|
@ -22,7 +22,7 @@ use crate::{
|
||||||
util::{self, IdRef},
|
util::{self, IdRef},
|
||||||
window::{get_window_id, UnownedWindow},
|
window::{get_window_id, UnownedWindow},
|
||||||
},
|
},
|
||||||
window::WindowId,
|
window::{Fullscreen, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct WindowDelegateState {
|
pub struct WindowDelegateState {
|
||||||
|
@ -84,11 +84,7 @@ impl WindowDelegateState {
|
||||||
pub fn emit_resize_event(&mut self) {
|
pub fn emit_resize_event(&mut self) {
|
||||||
let rect = unsafe { NSView::frame(*self.ns_view) };
|
let rect = unsafe { NSView::frame(*self.ns_view) };
|
||||||
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
|
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
|
||||||
let event = Event::WindowEvent {
|
self.emit_event(WindowEvent::Resized(size));
|
||||||
window_id: WindowId(get_window_id(*self.ns_window)),
|
|
||||||
event: WindowEvent::Resized(size),
|
|
||||||
};
|
|
||||||
AppState::send_event_immediately(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_move_event(&mut self) {
|
fn emit_move_event(&mut self) {
|
||||||
|
@ -182,6 +178,11 @@ lazy_static! {
|
||||||
dragging_exited as extern "C" fn(&Object, Sel, id),
|
dragging_exited as extern "C" fn(&Object, Sel, id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
decl.add_method(
|
||||||
|
sel!(window:willUseFullScreenPresentationOptions:),
|
||||||
|
window_will_use_fullscreen_presentation_options
|
||||||
|
as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
||||||
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(windowDidEnterFullScreen:),
|
sel!(windowDidEnterFullScreen:),
|
||||||
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
|
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
|
||||||
|
@ -408,6 +409,26 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||||
trace!("Completed `windowWillEnterFullscreen:`");
|
trace!("Completed `windowWillEnterFullscreen:`");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn window_will_use_fullscreen_presentation_options(
|
||||||
|
_this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
_: id,
|
||||||
|
_proposed_options: NSUInteger,
|
||||||
|
) -> NSUInteger {
|
||||||
|
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||||
|
// this would be configurable by the user. Unfortunately because of our
|
||||||
|
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||||
|
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
||||||
|
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
||||||
|
// still want to make this configurable for borderless fullscreen. Right now
|
||||||
|
// we don't, for consistency. If we do, it should be documented that the
|
||||||
|
// user-provided options are ignored in exclusive fullscreen.
|
||||||
|
(NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||||
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||||
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
|
||||||
|
.bits()
|
||||||
|
}
|
||||||
|
|
||||||
/// Invoked when entered fullscreen
|
/// Invoked when entered fullscreen
|
||||||
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||||
trace!("Triggered `windowDidEnterFullscreen:`");
|
trace!("Triggered `windowDidEnterFullscreen:`");
|
||||||
|
@ -415,8 +436,21 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||||
state.with_window(|window| {
|
state.with_window(|window| {
|
||||||
let monitor = window.current_monitor();
|
let monitor = window.current_monitor();
|
||||||
trace!("Locked shared state in `window_did_enter_fullscreen`");
|
trace!("Locked shared state in `window_did_enter_fullscreen`");
|
||||||
window.shared_state.lock().unwrap().fullscreen = Some(monitor);
|
let mut shared_state = window.shared_state.lock().unwrap();
|
||||||
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
|
match shared_state.fullscreen {
|
||||||
|
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||||
|
// can't enter exclusive mode by other means (like the
|
||||||
|
// fullscreen button on the window decorations)
|
||||||
|
Some(Fullscreen::Exclusive(_)) => (),
|
||||||
|
// `window_did_enter_fullscreen` was triggered and we're already
|
||||||
|
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||||
|
// as it updates the state
|
||||||
|
Some(Fullscreen::Borderless(_)) => (),
|
||||||
|
// Otherwise, we must've reached fullscreen by the user clicking
|
||||||
|
// on the green fullscreen button. Update state!
|
||||||
|
None => shared_state.fullscreen = Some(Fullscreen::Borderless(monitor)),
|
||||||
|
}
|
||||||
|
trace!("Unlocked shared state in `window_did_enter_fullscreen`");
|
||||||
});
|
});
|
||||||
state.initial_fullscreen = false;
|
state.initial_fullscreen = false;
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,9 +21,6 @@ mod platform;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
#[path = "ios/mod.rs"]
|
#[path = "ios/mod.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
#[cfg(target_os = "emscripten")]
|
|
||||||
#[path = "emscripten/mod.rs"]
|
|
||||||
mod platform;
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[path = "web/mod.rs"]
|
#[path = "web/mod.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
@ -38,7 +35,6 @@ mod platform;
|
||||||
not(target_os = "freebsd"),
|
not(target_os = "freebsd"),
|
||||||
not(target_os = "netbsd"),
|
not(target_os = "netbsd"),
|
||||||
not(target_os = "openbsd"),
|
not(target_os = "openbsd"),
|
||||||
not(target_os = "emscripten"),
|
|
||||||
not(target_arch = "wasm32"),
|
not(target_arch = "wasm32"),
|
||||||
))]
|
))]
|
||||||
compile_error!("The platform you're compiling for is not supported by winit");
|
compile_error!("The platform you're compiling for is not supported by winit");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![allow(non_snake_case, unused_unsafe)]
|
#![allow(non_snake_case, unused_unsafe)]
|
||||||
|
|
||||||
use std::{mem, os::raw::c_void, sync::Once};
|
use std::sync::Once;
|
||||||
|
|
||||||
use winapi::{
|
use winapi::{
|
||||||
shared::{
|
shared::{
|
||||||
|
@ -9,13 +9,12 @@ use winapi::{
|
||||||
winerror::S_OK,
|
winerror::S_OK,
|
||||||
},
|
},
|
||||||
um::{
|
um::{
|
||||||
libloaderapi::{GetProcAddress, LoadLibraryA},
|
|
||||||
shellscalingapi::{
|
shellscalingapi::{
|
||||||
MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
|
MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
|
||||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||||
},
|
},
|
||||||
wingdi::{GetDeviceCaps, LOGPIXELSX},
|
wingdi::{GetDeviceCaps, LOGPIXELSX},
|
||||||
winnt::{HRESULT, LPCSTR},
|
winnt::HRESULT,
|
||||||
winuser::{self, MONITOR_DEFAULTTONEAREST},
|
winuser::{self, MONITOR_DEFAULTTONEAREST},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -35,33 +34,6 @@ type GetDpiForMonitor = unsafe extern "system" fn(
|
||||||
) -> HRESULT;
|
) -> HRESULT;
|
||||||
type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
|
type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
|
||||||
|
|
||||||
// Helper function to dynamically load function pointer.
|
|
||||||
// `library` and `function` must be zero-terminated.
|
|
||||||
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
|
||||||
assert_eq!(library.chars().last(), Some('\0'));
|
|
||||||
assert_eq!(function.chars().last(), Some('\0'));
|
|
||||||
|
|
||||||
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
|
|
||||||
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
|
|
||||||
if module.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
|
|
||||||
if function_ptr.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(function_ptr as _)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! get_function {
|
|
||||||
($lib:expr, $func:ident) => {
|
|
||||||
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
|
|
||||||
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> =
|
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> =
|
||||||
get_function!("user32.dll", GetDpiForWindow);
|
get_function!("user32.dll", GetDpiForWindow);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
//! An events loop on Win32 is a background thread.
|
//! An events loop on Win32 is a background thread.
|
||||||
//!
|
//!
|
||||||
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
|
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
|
||||||
|
@ -38,14 +39,14 @@ use winapi::{
|
||||||
},
|
},
|
||||||
um::{
|
um::{
|
||||||
commctrl, libloaderapi, ole2, processthreadsapi, winbase,
|
commctrl, libloaderapi, ole2, processthreadsapi, winbase,
|
||||||
winnt::{LONG, LPCSTR, SHORT},
|
winnt::{HANDLE, LONG, LPCSTR, SHORT},
|
||||||
winuser,
|
winuser,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
|
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
|
||||||
event::{DeviceEvent, Event, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent},
|
event::{DeviceEvent, Event, Force, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||||
platform_impl::platform::{
|
platform_impl::platform::{
|
||||||
dpi::{
|
dpi::{
|
||||||
|
@ -62,6 +63,39 @@ use crate::{
|
||||||
window::WindowId as RootWindowId,
|
window::WindowId as RootWindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||||
|
pointerId: UINT,
|
||||||
|
entriesCount: *mut UINT,
|
||||||
|
pointerCount: *mut UINT,
|
||||||
|
pointerInfo: *mut winuser::POINTER_INFO,
|
||||||
|
) -> BOOL;
|
||||||
|
|
||||||
|
type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: UINT) -> BOOL;
|
||||||
|
type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||||
|
device: HANDLE,
|
||||||
|
pointerDeviceRect: *mut RECT,
|
||||||
|
displayRect: *mut RECT,
|
||||||
|
) -> BOOL;
|
||||||
|
|
||||||
|
type GetPointerTouchInfo =
|
||||||
|
unsafe extern "system" fn(pointerId: UINT, touchInfo: *mut winuser::POINTER_TOUCH_INFO) -> BOOL;
|
||||||
|
|
||||||
|
type GetPointerPenInfo =
|
||||||
|
unsafe extern "system" fn(pointId: UINT, penInfo: *mut winuser::POINTER_PEN_INFO) -> BOOL;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref GET_POINTER_FRAME_INFO_HISTORY: Option<GetPointerFrameInfoHistory> =
|
||||||
|
get_function!("user32.dll", GetPointerFrameInfoHistory);
|
||||||
|
static ref SKIP_POINTER_FRAME_MESSAGES: Option<SkipPointerFrameMessages> =
|
||||||
|
get_function!("user32.dll", SkipPointerFrameMessages);
|
||||||
|
static ref GET_POINTER_DEVICE_RECTS: Option<GetPointerDeviceRects> =
|
||||||
|
get_function!("user32.dll", GetPointerDeviceRects);
|
||||||
|
static ref GET_POINTER_TOUCH_INFO: Option<GetPointerTouchInfo> =
|
||||||
|
get_function!("user32.dll", GetPointerTouchInfo);
|
||||||
|
static ref GET_POINTER_PEN_INFO: Option<GetPointerPenInfo> =
|
||||||
|
get_function!("user32.dll", GetPointerPenInfo);
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct SubclassInput<T> {
|
pub(crate) struct SubclassInput<T> {
|
||||||
pub window_state: Arc<Mutex<WindowState>>,
|
pub window_state: Arc<Mutex<WindowState>>,
|
||||||
pub event_loop_runner: EventLoopRunnerShared<T>,
|
pub event_loop_runner: EventLoopRunnerShared<T>,
|
||||||
|
@ -654,13 +688,21 @@ impl EventLoopThreadExecutor {
|
||||||
|
|
||||||
type ThreadExecFn = Box<Box<dyn FnMut()>>;
|
type ThreadExecFn = Box<Box<dyn FnMut()>>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
pub struct EventLoopProxy<T: 'static> {
|
||||||
target_window: HWND,
|
target_window: HWND,
|
||||||
event_send: Sender<T>,
|
event_send: Sender<T>,
|
||||||
}
|
}
|
||||||
unsafe impl<T: Send + 'static> Send for EventLoopProxy<T> {}
|
unsafe impl<T: Send + 'static> Send for EventLoopProxy<T> {}
|
||||||
|
|
||||||
|
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
target_window: self.target_window,
|
||||||
|
event_send: self.event_send.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
impl<T: 'static> EventLoopProxy<T> {
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -820,6 +862,13 @@ pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>)
|
||||||
assert_eq!(subclass_result, 1);
|
assert_eq!(subclass_result, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
|
||||||
|
match pressure {
|
||||||
|
1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Any window whose callback is configured to this function will have its events propagated
|
/// Any window whose callback is configured to this function will have its events propagated
|
||||||
/// through the events loop of the thread the window was created in.
|
/// through the events loop of the thread the window was created in.
|
||||||
//
|
//
|
||||||
|
@ -982,11 +1031,34 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||||
|
|
||||||
winuser::WM_CHAR => {
|
winuser::WM_CHAR => {
|
||||||
use crate::event::WindowEvent::ReceivedCharacter;
|
use crate::event::WindowEvent::ReceivedCharacter;
|
||||||
let chr: char = mem::transmute(wparam as u32);
|
use std::char;
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF;
|
||||||
window_id: RootWindowId(WindowId(window)),
|
let is_low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF;
|
||||||
event: ReceivedCharacter(chr),
|
|
||||||
});
|
if is_high_surrogate {
|
||||||
|
subclass_input.window_state.lock().high_surrogate = Some(wparam as u16);
|
||||||
|
} else if is_low_surrogate {
|
||||||
|
let high_surrogate = subclass_input.window_state.lock().high_surrogate.take();
|
||||||
|
|
||||||
|
if let Some(high_surrogate) = high_surrogate {
|
||||||
|
let pair = [high_surrogate, wparam as u16];
|
||||||
|
if let Some(Ok(chr)) = char::decode_utf16(pair.iter().copied()).next() {
|
||||||
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(WindowId(window)),
|
||||||
|
event: ReceivedCharacter(chr),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subclass_input.window_state.lock().high_surrogate = None;
|
||||||
|
|
||||||
|
if let Some(chr) = char::from_u32(wparam as u32) {
|
||||||
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(WindowId(window)),
|
||||||
|
event: ReceivedCharacter(chr),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -996,6 +1068,17 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||||
// other unwanted default hotkeys as well.
|
// other unwanted default hotkeys as well.
|
||||||
winuser::WM_SYSCHAR => 0,
|
winuser::WM_SYSCHAR => 0,
|
||||||
|
|
||||||
|
winuser::WM_SYSCOMMAND => {
|
||||||
|
if wparam == winuser::SC_SCREENSAVE {
|
||||||
|
let window_state = subclass_input.window_state.lock();
|
||||||
|
if window_state.fullscreen.is_some() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
winuser::DefWindowProcW(window, msg, wparam, lparam)
|
||||||
|
}
|
||||||
|
|
||||||
winuser::WM_MOUSEMOVE => {
|
winuser::WM_MOUSEMOVE => {
|
||||||
use crate::event::WindowEvent::{CursorEntered, CursorMoved};
|
use crate::event::WindowEvent::{CursorEntered, CursorMoved};
|
||||||
let mouse_was_outside_window = {
|
let mouse_was_outside_window = {
|
||||||
|
@ -1431,8 +1514,17 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||||
{
|
{
|
||||||
let dpi_factor = hwnd_scale_factor(window);
|
let dpi_factor = hwnd_scale_factor(window);
|
||||||
for input in &inputs {
|
for input in &inputs {
|
||||||
let x = (input.x as f64) / 100f64;
|
let mut location = POINT {
|
||||||
let y = (input.y as f64) / 100f64;
|
x: input.x / 100,
|
||||||
|
y: input.y / 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
if winuser::ScreenToClient(window, &mut location as *mut _) == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = location.x as f64 + (input.x % 100) as f64 / 100f64;
|
||||||
|
let y = location.y as f64 + (input.y % 100) as f64 / 100f64;
|
||||||
let location = LogicalPosition::from_physical((x, y), dpi_factor);
|
let location = LogicalPosition::from_physical((x, y), dpi_factor);
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
window_id: RootWindowId(WindowId(window)),
|
window_id: RootWindowId(WindowId(window)),
|
||||||
|
@ -1447,6 +1539,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
location,
|
location,
|
||||||
|
force: None, // WM_TOUCH doesn't support pressure information
|
||||||
id: input.dwID as u64,
|
id: input.dwID as u64,
|
||||||
device_id: DEVICE_ID,
|
device_id: DEVICE_ID,
|
||||||
}),
|
}),
|
||||||
|
@ -1457,6 +1550,147 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => {
|
||||||
|
if let (
|
||||||
|
Some(GetPointerFrameInfoHistory),
|
||||||
|
Some(SkipPointerFrameMessages),
|
||||||
|
Some(GetPointerDeviceRects),
|
||||||
|
) = (
|
||||||
|
*GET_POINTER_FRAME_INFO_HISTORY,
|
||||||
|
*SKIP_POINTER_FRAME_MESSAGES,
|
||||||
|
*GET_POINTER_DEVICE_RECTS,
|
||||||
|
) {
|
||||||
|
let pointer_id = LOWORD(wparam as DWORD) as UINT;
|
||||||
|
let mut entries_count = 0 as UINT;
|
||||||
|
let mut pointers_count = 0 as UINT;
|
||||||
|
if GetPointerFrameInfoHistory(
|
||||||
|
pointer_id,
|
||||||
|
&mut entries_count as *mut _,
|
||||||
|
&mut pointers_count as *mut _,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
) == 0
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pointer_info_count = (entries_count * pointers_count) as usize;
|
||||||
|
let mut pointer_infos = Vec::with_capacity(pointer_info_count);
|
||||||
|
pointer_infos.set_len(pointer_info_count);
|
||||||
|
if GetPointerFrameInfoHistory(
|
||||||
|
pointer_id,
|
||||||
|
&mut entries_count as *mut _,
|
||||||
|
&mut pointers_count as *mut _,
|
||||||
|
pointer_infos.as_mut_ptr(),
|
||||||
|
) == 0
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dpi_factor = hwnd_scale_factor(window);
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory
|
||||||
|
// The information retrieved appears in reverse chronological order, with the most recent entry in the first
|
||||||
|
// row of the returned array
|
||||||
|
for pointer_info in pointer_infos.iter().rev() {
|
||||||
|
let mut device_rect = mem::MaybeUninit::uninit();
|
||||||
|
let mut display_rect = mem::MaybeUninit::uninit();
|
||||||
|
|
||||||
|
if (GetPointerDeviceRects(
|
||||||
|
pointer_info.sourceDevice,
|
||||||
|
device_rect.as_mut_ptr(),
|
||||||
|
display_rect.as_mut_ptr(),
|
||||||
|
)) == 0
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device_rect = device_rect.assume_init();
|
||||||
|
let display_rect = display_rect.assume_init();
|
||||||
|
|
||||||
|
// For the most precise himetric to pixel conversion we calculate the ratio between the resolution
|
||||||
|
// of the display device (pixel) and the touch device (himetric).
|
||||||
|
let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64
|
||||||
|
/ (device_rect.right - device_rect.left) as f64;
|
||||||
|
let himetric_to_pixel_ratio_y = (display_rect.bottom - display_rect.top) as f64
|
||||||
|
/ (device_rect.bottom - device_rect.top) as f64;
|
||||||
|
|
||||||
|
// ptHimetricLocation's origin is 0,0 even on multi-monitor setups.
|
||||||
|
// On multi-monitor setups we need to translate the himetric location to the rect of the
|
||||||
|
// display device it's attached to.
|
||||||
|
let x = display_rect.left as f64
|
||||||
|
+ pointer_info.ptHimetricLocation.x as f64 * himetric_to_pixel_ratio_x;
|
||||||
|
let y = display_rect.top as f64
|
||||||
|
+ pointer_info.ptHimetricLocation.y as f64 * himetric_to_pixel_ratio_y;
|
||||||
|
|
||||||
|
let mut location = POINT {
|
||||||
|
x: x.floor() as i32,
|
||||||
|
y: y.floor() as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
if winuser::ScreenToClient(window, &mut location as *mut _) == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let force = match pointer_info.pointerType {
|
||||||
|
winuser::PT_TOUCH => {
|
||||||
|
let mut touch_info = mem::MaybeUninit::uninit();
|
||||||
|
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||||
|
match GetPointerTouchInfo(
|
||||||
|
pointer_info.pointerId,
|
||||||
|
touch_info.as_mut_ptr(),
|
||||||
|
) {
|
||||||
|
0 => None,
|
||||||
|
_ => normalize_pointer_pressure(
|
||||||
|
touch_info.assume_init().pressure,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
winuser::PT_PEN => {
|
||||||
|
let mut pen_info = mem::MaybeUninit::uninit();
|
||||||
|
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||||
|
match GetPointerPenInfo(
|
||||||
|
pointer_info.pointerId,
|
||||||
|
pen_info.as_mut_ptr(),
|
||||||
|
) {
|
||||||
|
0 => None,
|
||||||
|
_ => {
|
||||||
|
normalize_pointer_pressure(pen_info.assume_init().pressure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = location.x as f64 + x.fract();
|
||||||
|
let y = location.y as f64 + y.fract();
|
||||||
|
let location = LogicalPosition::from_physical((x, y), dpi_factor);
|
||||||
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
|
window_id: RootWindowId(WindowId(window)),
|
||||||
|
event: WindowEvent::Touch(Touch {
|
||||||
|
phase: if pointer_info.pointerFlags & winuser::POINTER_FLAG_DOWN != 0 {
|
||||||
|
TouchPhase::Started
|
||||||
|
} else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UP != 0 {
|
||||||
|
TouchPhase::Ended
|
||||||
|
} else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UPDATE != 0
|
||||||
|
{
|
||||||
|
TouchPhase::Moved
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
force,
|
||||||
|
id: pointer_info.pointerId as u64,
|
||||||
|
device_id: DEVICE_ID,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SkipPointerFrameMessages(pointer_id);
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
winuser::WM_SETFOCUS => {
|
winuser::WM_SETFOCUS => {
|
||||||
use crate::event::WindowEvent::Focused;
|
use crate::event::WindowEvent::Focused;
|
||||||
subclass_input.send_event(Event::WindowEvent {
|
subclass_input.send_event(Event::WindowEvent {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::Window,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,6 +67,8 @@ impl WindowId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod util;
|
||||||
mod dpi;
|
mod dpi;
|
||||||
mod drop_handler;
|
mod drop_handler;
|
||||||
mod event;
|
mod event;
|
||||||
|
@ -74,6 +76,5 @@ mod event_loop;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
mod raw_input;
|
mod raw_input;
|
||||||
mod util;
|
|
||||||
mod window;
|
mod window;
|
||||||
mod window_state;
|
mod window_state;
|
||||||
|
|
|
@ -3,54 +3,64 @@ use winapi::{
|
||||||
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
|
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
|
||||||
windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
|
windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
|
||||||
},
|
},
|
||||||
um::{wingdi, winnt::LONG, winuser},
|
um::{wingdi, winuser},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashSet, VecDeque},
|
collections::{BTreeSet, VecDeque},
|
||||||
io, mem, ptr,
|
io, mem, ptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{util, EventLoop};
|
use super::{util, EventLoop};
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
platform_impl::platform::{
|
platform_impl::platform::{
|
||||||
dpi::{dpi_to_scale_factor, get_monitor_dpi},
|
dpi::{dpi_to_scale_factor, get_monitor_dpi},
|
||||||
window::Window,
|
window::Window,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Win32 implementation of the main `MonitorHandle` object.
|
|
||||||
#[derive(Derivative)]
|
#[derive(Derivative)]
|
||||||
#[derivative(Debug, Clone)]
|
#[derivative(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct MonitorHandle {
|
pub struct VideoMode {
|
||||||
/// Monitor handle.
|
pub(crate) size: (u32, u32),
|
||||||
hmonitor: HMonitor,
|
pub(crate) bit_depth: u16,
|
||||||
#[derivative(Debug = "ignore")]
|
pub(crate) refresh_rate: u16,
|
||||||
monitor_info: winuser::MONITORINFOEXW,
|
pub(crate) monitor: MonitorHandle,
|
||||||
/// The system name of the monitor.
|
#[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
|
||||||
monitor_name: String,
|
pub(crate) native_video_mode: wingdi::DEVMODEW,
|
||||||
/// True if this is the primary monitor.
|
|
||||||
primary: bool,
|
|
||||||
/// The position of the monitor in pixels on the desktop.
|
|
||||||
///
|
|
||||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
|
||||||
position: (i32, i32),
|
|
||||||
/// The current resolution in pixels on the monitor.
|
|
||||||
dimensions: (u32, u32),
|
|
||||||
/// DPI scale factor.
|
|
||||||
hidpi_factor: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
self.bit_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
self.refresh_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
RootMonitorHandle {
|
||||||
|
inner: self.monitor.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct MonitorHandle(HMONITOR);
|
||||||
|
|
||||||
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
||||||
// For more info see:
|
// For more info see:
|
||||||
// https://github.com/retep998/winapi-rs/issues/360
|
// https://github.com/retep998/winapi-rs/issues/360
|
||||||
// https://github.com/retep998/winapi-rs/issues/396
|
// https://github.com/retep998/winapi-rs/issues/396
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct HMonitor(HMONITOR);
|
|
||||||
|
|
||||||
unsafe impl Send for HMonitor {}
|
unsafe impl Send for MonitorHandle {}
|
||||||
|
|
||||||
unsafe extern "system" fn monitor_enum_proc(
|
unsafe extern "system" fn monitor_enum_proc(
|
||||||
hmonitor: HMONITOR,
|
hmonitor: HMONITOR,
|
||||||
|
@ -59,7 +69,7 @@ unsafe extern "system" fn monitor_enum_proc(
|
||||||
data: LPARAM,
|
data: LPARAM,
|
||||||
) -> BOOL {
|
) -> BOOL {
|
||||||
let monitors = data as *mut VecDeque<MonitorHandle>;
|
let monitors = data as *mut VecDeque<MonitorHandle>;
|
||||||
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor));
|
(*monitors).push_back(MonitorHandle::new(hmonitor));
|
||||||
TRUE // continue enumeration
|
TRUE // continue enumeration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,12 +89,12 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||||
pub fn primary_monitor() -> MonitorHandle {
|
pub fn primary_monitor() -> MonitorHandle {
|
||||||
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
||||||
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
|
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
|
||||||
MonitorHandle::from_hmonitor(hmonitor)
|
MonitorHandle::new(hmonitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
|
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
|
||||||
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
|
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
|
||||||
MonitorHandle::from_hmonitor(hmonitor)
|
MonitorHandle::new(hmonitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
impl<T> EventLoop<T> {
|
||||||
|
@ -125,73 +135,69 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINF
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonitorHandle {
|
impl MonitorHandle {
|
||||||
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
|
pub(crate) fn new(hmonitor: HMONITOR) -> Self {
|
||||||
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
|
MonitorHandle(hmonitor)
|
||||||
let place = monitor_info.rcMonitor;
|
|
||||||
let dimensions = (
|
|
||||||
(place.right - place.left) as u32,
|
|
||||||
(place.bottom - place.top) as u32,
|
|
||||||
);
|
|
||||||
MonitorHandle {
|
|
||||||
hmonitor: HMonitor(hmonitor),
|
|
||||||
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
|
|
||||||
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
|
|
||||||
position: (place.left as i32, place.top as i32),
|
|
||||||
dimensions,
|
|
||||||
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
|
|
||||||
monitor_info,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
||||||
let left = self.position.0 as LONG;
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
let right = left + self.dimensions.0 as LONG;
|
point.x >= monitor_info.rcMonitor.left
|
||||||
let top = self.position.1 as LONG;
|
&& point.x <= monitor_info.rcMonitor.right
|
||||||
let bottom = top + self.dimensions.1 as LONG;
|
&& point.y >= monitor_info.rcMonitor.top
|
||||||
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
|
&& point.y <= monitor_info.rcMonitor.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> Option<String> {
|
pub fn name(&self) -> Option<String> {
|
||||||
Some(self.monitor_name.clone())
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
Some(util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn native_identifier(&self) -> String {
|
pub fn native_identifier(&self) -> String {
|
||||||
self.monitor_name.clone()
|
self.name().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn hmonitor(&self) -> HMONITOR {
|
pub fn hmonitor(&self) -> HMONITOR {
|
||||||
self.hmonitor.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn size(&self) -> PhysicalSize {
|
pub fn size(&self) -> PhysicalSize {
|
||||||
self.dimensions.into()
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
PhysicalSize {
|
||||||
|
width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as f64,
|
||||||
|
height: (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as f64,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn position(&self) -> PhysicalPosition {
|
pub fn position(&self) -> PhysicalPosition {
|
||||||
self.position.into()
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
PhysicalPosition {
|
||||||
|
x: monitor_info.rcMonitor.left as f64,
|
||||||
|
y: monitor_info.rcMonitor.top as f64,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn hidpi_factor(&self) -> f64 {
|
pub fn hidpi_factor(&self) -> f64 {
|
||||||
self.hidpi_factor
|
dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
||||||
// fields are probably changing, but we aren't looking at those fields
|
// fields are probably changing, but we aren't looking at those fields
|
||||||
// anyway), so we're using a HashSet deduplicate
|
// anyway), so we're using a BTreeSet deduplicate
|
||||||
let mut modes = HashSet::new();
|
let mut modes = BTreeSet::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
unsafe {
|
unsafe {
|
||||||
let device_name = self.monitor_info.szDevice.as_ptr();
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
let device_name = monitor_info.szDevice.as_ptr();
|
||||||
let mut mode: wingdi::DEVMODEW = mem::zeroed();
|
let mut mode: wingdi::DEVMODEW = mem::zeroed();
|
||||||
mode.dmSize = mem::size_of_val(&mode) as WORD;
|
mode.dmSize = mem::size_of_val(&mode) as WORD;
|
||||||
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
|
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
|
||||||
|
@ -205,10 +211,14 @@ impl MonitorHandle {
|
||||||
| wingdi::DM_DISPLAYFREQUENCY;
|
| wingdi::DM_DISPLAYFREQUENCY;
|
||||||
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
|
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
|
||||||
|
|
||||||
modes.insert(VideoMode {
|
modes.insert(RootVideoMode {
|
||||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
video_mode: VideoMode {
|
||||||
bit_depth: mode.dmBitsPerPel as u16,
|
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||||
refresh_rate: mode.dmDisplayFrequency as u16,
|
bit_depth: mode.dmBitsPerPel as u16,
|
||||||
|
refresh_rate: mode.dmDisplayFrequency as u16,
|
||||||
|
monitor: self.clone(),
|
||||||
|
native_video_mode: mode,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
io, mem,
|
io, mem,
|
||||||
ops::BitAnd,
|
ops::BitAnd,
|
||||||
|
os::raw::c_void,
|
||||||
ptr, slice,
|
ptr, slice,
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
};
|
};
|
||||||
|
@ -12,9 +13,44 @@ use winapi::{
|
||||||
minwindef::{BOOL, DWORD},
|
minwindef::{BOOL, DWORD},
|
||||||
windef::{HWND, POINT, RECT},
|
windef::{HWND, POINT, RECT},
|
||||||
},
|
},
|
||||||
um::{winbase::lstrlenW, winuser},
|
um::{
|
||||||
|
libloaderapi::{GetProcAddress, LoadLibraryA},
|
||||||
|
winbase::lstrlenW,
|
||||||
|
winnt::LPCSTR,
|
||||||
|
winuser,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to dynamically load function pointer.
|
||||||
|
// `library` and `function` must be zero-terminated.
|
||||||
|
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
||||||
|
assert_eq!(library.chars().last(), Some('\0'));
|
||||||
|
assert_eq!(function.chars().last(), Some('\0'));
|
||||||
|
|
||||||
|
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
|
||||||
|
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
|
||||||
|
if module.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
|
||||||
|
if function_ptr.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(function_ptr as _)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! get_function {
|
||||||
|
($lib:expr, $func:ident) => {
|
||||||
|
crate::platform_impl::platform::util::get_function_impl(
|
||||||
|
concat!($lib, '\0'),
|
||||||
|
concat!(stringify!($func), '\0'),
|
||||||
|
)
|
||||||
|
.map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||||
where
|
where
|
||||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||||
|
@ -105,6 +141,16 @@ pub fn set_cursor_hidden(hidden: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cursor_clip() -> Result<RECT, io::Error> {
|
||||||
|
unsafe {
|
||||||
|
let mut rect: RECT = mem::zeroed();
|
||||||
|
win_to_err(|| winuser::GetClipCursor(&mut rect)).map(|_| rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the cursor's clip rect.
|
||||||
|
///
|
||||||
|
/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event.
|
||||||
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
|
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let rect_ptr = rect
|
let rect_ptr = rect
|
||||||
|
@ -115,6 +161,19 @@ pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_desktop_rect() -> RECT {
|
||||||
|
unsafe {
|
||||||
|
let left = winuser::GetSystemMetrics(winuser::SM_XVIRTUALSCREEN);
|
||||||
|
let top = winuser::GetSystemMetrics(winuser::SM_YVIRTUALSCREEN);
|
||||||
|
RECT {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
right: left + winuser::GetSystemMetrics(winuser::SM_CXVIRTUALSCREEN),
|
||||||
|
bottom: top + winuser::GetSystemMetrics(winuser::SM_CYVIRTUALSCREEN),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_focused(window: HWND) -> bool {
|
pub fn is_focused(window: HWND) -> bool {
|
||||||
window == unsafe { winuser::GetActiveWindow() }
|
window == unsafe { winuser::GetActiveWindow() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#![cfg(target_os = "windows")]
|
#![cfg(target_os = "windows")]
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use raw_window_handle::{windows::WindowsHandle, RawWindowHandle};
|
||||||
use std::{
|
use std::{
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
|
@ -46,7 +47,7 @@ use crate::{
|
||||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||||
PlatformSpecificWindowBuilderAttributes, WindowId,
|
PlatformSpecificWindowBuilderAttributes, WindowId,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, Icon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The Win32 implementation of the main `Window` object.
|
/// The Win32 implementation of the main `Window` object.
|
||||||
|
@ -211,6 +212,15 @@ impl Window {
|
||||||
pub fn set_outer_position(&self, logical_position: LogicalPosition) {
|
pub fn set_outer_position(&self, logical_position: LogicalPosition) {
|
||||||
let dpi_factor = self.hidpi_factor();
|
let dpi_factor = self.hidpi_factor();
|
||||||
let (x, y) = logical_position.to_physical(dpi_factor).into();
|
let (x, y) = logical_position.to_physical(dpi_factor).into();
|
||||||
|
|
||||||
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
let window = self.window.clone();
|
||||||
|
self.thread_executor.execute_in_thread(move || {
|
||||||
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
|
f.set(WindowFlags::MAXIMIZED, false)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
self.set_position_physical(x, y);
|
self.set_position_physical(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +296,15 @@ impl Window {
|
||||||
pub fn set_inner_size(&self, logical_size: LogicalSize) {
|
pub fn set_inner_size(&self, logical_size: LogicalSize) {
|
||||||
let dpi_factor = self.hidpi_factor();
|
let dpi_factor = self.hidpi_factor();
|
||||||
let (width, height) = logical_size.to_physical(dpi_factor).into();
|
let (width, height) = logical_size.to_physical(dpi_factor).into();
|
||||||
|
|
||||||
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
let window = self.window.clone();
|
||||||
|
self.thread_executor.execute_in_thread(move || {
|
||||||
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
|
f.set(WindowFlags::MAXIMIZED, false)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
self.set_inner_size_physical(width, height);
|
self.set_inner_size_physical(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +346,7 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
f.set(WindowFlags::RESIZABLE, resizable)
|
f.set(WindowFlags::RESIZABLE, resizable)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -339,6 +358,15 @@ impl Window {
|
||||||
self.window.0
|
self.window.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
let handle = WindowsHandle {
|
||||||
|
hwnd: self.window.0 as *mut _,
|
||||||
|
..WindowsHandle::empty()
|
||||||
|
};
|
||||||
|
RawWindowHandle::Windows(handle)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||||
self.window_state.lock().mouse.cursor = cursor;
|
self.window_state.lock().mouse.cursor = cursor;
|
||||||
|
@ -421,80 +449,178 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
f.set(WindowFlags::MAXIMIZED, maximized)
|
f.set(WindowFlags::MAXIMIZED, maximized)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
let window_state = self.window_state.lock();
|
let window_state = self.window_state.lock();
|
||||||
window_state.fullscreen.clone()
|
window_state.fullscreen.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
unsafe {
|
let window = self.window.clone();
|
||||||
let window = self.window.clone();
|
let window_state = Arc::clone(&self.window_state);
|
||||||
let window_state = Arc::clone(&self.window_state);
|
|
||||||
|
|
||||||
match &monitor {
|
let mut window_state_lock = window_state.lock();
|
||||||
&Some(RootMonitorHandle { ref inner }) => {
|
let old_fullscreen = window_state_lock.fullscreen.clone();
|
||||||
let (x, y): (i32, i32) = inner.position().into();
|
if window_state_lock.fullscreen == fullscreen {
|
||||||
let (width, height): (u32, u32) = inner.size().into();
|
return;
|
||||||
|
}
|
||||||
|
window_state_lock.fullscreen = fullscreen.clone();
|
||||||
|
drop(window_state_lock);
|
||||||
|
|
||||||
let mut monitor = monitor.clone();
|
self.thread_executor.execute_in_thread(move || {
|
||||||
self.thread_executor.execute_in_thread(move || {
|
let mut window_state_lock = window_state.lock();
|
||||||
let mut window_state_lock = window_state.lock();
|
|
||||||
|
|
||||||
let client_rect =
|
// Save window bounds before entering fullscreen
|
||||||
util::get_client_rect(window.0).expect("get client rect failed!");
|
match (&old_fullscreen, &fullscreen) {
|
||||||
window_state_lock.saved_window = Some(SavedWindow {
|
(&None, &Some(_)) => {
|
||||||
client_rect,
|
let client_rect = util::get_client_rect(window.0).unwrap();
|
||||||
dpi_factor: window_state_lock.dpi_factor,
|
window_state_lock.saved_window = Some(SavedWindow {
|
||||||
});
|
client_rect,
|
||||||
|
dpi_factor: window_state_lock.dpi_factor,
|
||||||
window_state_lock.fullscreen = monitor.take();
|
|
||||||
WindowState::refresh_window_state(
|
|
||||||
window_state_lock,
|
|
||||||
window.0,
|
|
||||||
Some(RECT {
|
|
||||||
left: x,
|
|
||||||
top: y,
|
|
||||||
right: x + width as c_int,
|
|
||||||
bottom: y + height as c_int,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
mark_fullscreen(window.0, true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
&None => {
|
_ => (),
|
||||||
self.thread_executor.execute_in_thread(move || {
|
}
|
||||||
let mut window_state_lock = window_state.lock();
|
|
||||||
window_state_lock.fullscreen = None;
|
|
||||||
|
|
||||||
if let Some(SavedWindow {
|
// Change video mode if we're transitioning to or from exclusive
|
||||||
client_rect,
|
// fullscreen
|
||||||
dpi_factor,
|
match (&old_fullscreen, &fullscreen) {
|
||||||
}) = window_state_lock.saved_window
|
(&None, &Some(Fullscreen::Exclusive(ref video_mode)))
|
||||||
{
|
| (
|
||||||
window_state_lock.dpi_factor = dpi_factor;
|
&Some(Fullscreen::Borderless(_)),
|
||||||
window_state_lock.saved_window = None;
|
&Some(Fullscreen::Exclusive(ref video_mode)),
|
||||||
|
)
|
||||||
|
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) =>
|
||||||
|
{
|
||||||
|
let monitor = video_mode.monitor();
|
||||||
|
|
||||||
WindowState::refresh_window_state(
|
let mut display_name = OsStr::new(&monitor.inner.native_identifier())
|
||||||
window_state_lock,
|
.encode_wide()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// `encode_wide` does not add a null-terminator but
|
||||||
|
// `ChangeDisplaySettingsExW` requires a null-terminated
|
||||||
|
// string, so add it
|
||||||
|
display_name.push(0);
|
||||||
|
|
||||||
|
let mut native_video_mode = video_mode.video_mode.native_video_mode.clone();
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
winuser::ChangeDisplaySettingsExW(
|
||||||
|
display_name.as_ptr(),
|
||||||
|
&mut native_video_mode,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
winuser::CDS_FULLSCREEN,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
|
||||||
|
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
|
||||||
|
}
|
||||||
|
(&Some(Fullscreen::Exclusive(_)), &None)
|
||||||
|
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
|
||||||
|
let res = unsafe {
|
||||||
|
winuser::ChangeDisplaySettingsExW(
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
winuser::CDS_FULLSCREEN,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
|
||||||
|
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// There are some scenarios where calling `ChangeDisplaySettingsExW` takes long
|
||||||
|
// enough to execute that the DWM thinks our program has frozen and takes over
|
||||||
|
// our program's window. When that happens, the `SetWindowPos` call below gets
|
||||||
|
// eaten and the window doesn't get set to the proper fullscreen position.
|
||||||
|
//
|
||||||
|
// Calling `PeekMessageW` here notifies Windows that our process is still running
|
||||||
|
// fine, taking control back from the DWM and ensuring that the `SetWindowPos` call
|
||||||
|
// below goes through.
|
||||||
|
let mut msg = mem::zeroed();
|
||||||
|
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update window style
|
||||||
|
WindowState::set_window_flags(window_state_lock, window.0, |f| {
|
||||||
|
f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update window bounds
|
||||||
|
match &fullscreen {
|
||||||
|
Some(fullscreen) => {
|
||||||
|
let monitor = match fullscreen {
|
||||||
|
Fullscreen::Exclusive(ref video_mode) => video_mode.monitor(),
|
||||||
|
Fullscreen::Borderless(ref monitor) => monitor.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let position: (i32, i32) = monitor.position().into();
|
||||||
|
let size: (u32, u32) = monitor.size().into();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
winuser::SetWindowPos(
|
||||||
|
window.0,
|
||||||
|
ptr::null_mut(),
|
||||||
|
position.0,
|
||||||
|
position.1,
|
||||||
|
size.0 as i32,
|
||||||
|
size.1 as i32,
|
||||||
|
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
|
||||||
|
);
|
||||||
|
winuser::UpdateWindow(window.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut window_state_lock = window_state.lock();
|
||||||
|
if let Some(SavedWindow {
|
||||||
|
client_rect,
|
||||||
|
dpi_factor,
|
||||||
|
}) = window_state_lock.saved_window.take()
|
||||||
|
{
|
||||||
|
window_state_lock.dpi_factor = dpi_factor;
|
||||||
|
drop(window_state_lock);
|
||||||
|
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
winuser::SetWindowPos(
|
||||||
window.0,
|
window.0,
|
||||||
Some(client_rect),
|
ptr::null_mut(),
|
||||||
|
client_rect.left,
|
||||||
|
client_rect.top,
|
||||||
|
client_rect.right - client_rect.left,
|
||||||
|
client_rect.bottom - client_rect.top,
|
||||||
|
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
|
||||||
);
|
);
|
||||||
|
winuser::UpdateWindow(window.0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mark_fullscreen(window.0, false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
unsafe {
|
||||||
|
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -503,8 +629,7 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| {
|
|
||||||
f.set(WindowFlags::DECORATIONS, decorations)
|
f.set(WindowFlags::DECORATIONS, decorations)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -516,7 +641,7 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
|
f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -769,9 +894,7 @@ unsafe fn init<T: 'static>(
|
||||||
let window_state = {
|
let window_state = {
|
||||||
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
|
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
|
||||||
let window_state = Arc::new(Mutex::new(window_state));
|
let window_state = Arc::new(Mutex::new(window_state));
|
||||||
WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
|
||||||
*f = window_flags
|
|
||||||
});
|
|
||||||
window_state
|
window_state
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -865,7 +988,7 @@ pub fn com_initialized() {
|
||||||
// is activated. If the window is not fullscreen, the Shell falls back to
|
// is activated. If the window is not fullscreen, the Shell falls back to
|
||||||
// heuristics to determine how the window should be treated, which means
|
// heuristics to determine how the window should be treated, which means
|
||||||
// that it could still consider the window as fullscreen. :(
|
// that it could still consider the window as fullscreen. :(
|
||||||
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) {
|
unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) {
|
||||||
com_initialized();
|
com_initialized();
|
||||||
|
|
||||||
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
monitor::MonitorHandle,
|
|
||||||
platform_impl::platform::{event_loop, icon::WinIcon, util},
|
platform_impl::platform::{event_loop, icon::WinIcon, util},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||||
};
|
};
|
||||||
use parking_lot::MutexGuard;
|
use parking_lot::MutexGuard;
|
||||||
use std::{io, ptr};
|
use std::{io, ptr};
|
||||||
|
@ -29,10 +28,11 @@ pub struct WindowState {
|
||||||
pub saved_window: Option<SavedWindow>,
|
pub saved_window: Option<SavedWindow>,
|
||||||
pub dpi_factor: f64,
|
pub dpi_factor: f64,
|
||||||
|
|
||||||
pub fullscreen: Option<MonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
|
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
|
||||||
/// times in `EventsCleared`.
|
/// times in `EventsCleared`.
|
||||||
pub queued_out_of_band_redraw: bool,
|
pub queued_out_of_band_redraw: bool,
|
||||||
|
pub high_surrogate: Option<u16>,
|
||||||
window_flags: WindowFlags,
|
window_flags: WindowFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ bitflags! {
|
||||||
WindowFlags::RESIZABLE.bits |
|
WindowFlags::RESIZABLE.bits |
|
||||||
WindowFlags::MAXIMIZED.bits
|
WindowFlags::MAXIMIZED.bits
|
||||||
);
|
);
|
||||||
|
const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
|
||||||
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
||||||
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
||||||
}
|
}
|
||||||
|
@ -114,6 +115,7 @@ impl WindowState {
|
||||||
|
|
||||||
fullscreen: None,
|
fullscreen: None,
|
||||||
queued_out_of_band_redraw: false,
|
queued_out_of_band_redraw: false,
|
||||||
|
high_surrogate: None,
|
||||||
window_flags: WindowFlags::empty(),
|
window_flags: WindowFlags::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,32 +124,16 @@ impl WindowState {
|
||||||
self.window_flags
|
self.window_flags
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_window_flags<F>(
|
pub fn set_window_flags<F>(mut this: MutexGuard<'_, Self>, window: HWND, f: F)
|
||||||
mut this: MutexGuard<'_, Self>,
|
where
|
||||||
window: HWND,
|
|
||||||
set_client_rect: Option<RECT>,
|
|
||||||
f: F,
|
|
||||||
) where
|
|
||||||
F: FnOnce(&mut WindowFlags),
|
F: FnOnce(&mut WindowFlags),
|
||||||
{
|
{
|
||||||
let old_flags = this.window_flags;
|
let old_flags = this.window_flags;
|
||||||
f(&mut this.window_flags);
|
f(&mut this.window_flags);
|
||||||
|
|
||||||
let is_fullscreen = this.fullscreen.is_some();
|
|
||||||
this.window_flags
|
|
||||||
.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
|
|
||||||
let new_flags = this.window_flags;
|
let new_flags = this.window_flags;
|
||||||
|
|
||||||
drop(this);
|
drop(this);
|
||||||
old_flags.apply_diff(window, new_flags, set_client_rect);
|
old_flags.apply_diff(window, new_flags);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_window_state(
|
|
||||||
this: MutexGuard<'_, Self>,
|
|
||||||
window: HWND,
|
|
||||||
set_client_rect: Option<RECT>,
|
|
||||||
) {
|
|
||||||
Self::set_window_flags(this, window, set_client_rect, |_| ());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
||||||
|
@ -185,6 +171,7 @@ impl WindowFlags {
|
||||||
fn mask(mut self) -> WindowFlags {
|
fn mask(mut self) -> WindowFlags {
|
||||||
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
|
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||||
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
||||||
|
self |= WindowFlags::FULLSCREEN_OR_MASK;
|
||||||
}
|
}
|
||||||
if !self.contains(WindowFlags::VISIBLE) {
|
if !self.contains(WindowFlags::VISIBLE) {
|
||||||
self &= WindowFlags::INVISIBLE_AND_MASK;
|
self &= WindowFlags::INVISIBLE_AND_MASK;
|
||||||
|
@ -236,7 +223,7 @@ impl WindowFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the window client rectangle to the return value, if present.
|
/// Adjust the window client rectangle to the return value, if present.
|
||||||
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) {
|
fn apply_diff(mut self, window: HWND, mut new: WindowFlags) {
|
||||||
self = self.mask();
|
self = self.mask();
|
||||||
new = new.mask();
|
new = new.mask();
|
||||||
|
|
||||||
|
@ -295,45 +282,20 @@ impl WindowFlags {
|
||||||
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
|
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
|
||||||
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
|
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
|
||||||
|
|
||||||
match set_client_rect
|
let mut flags = winuser::SWP_NOZORDER
|
||||||
.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r))
|
| winuser::SWP_NOMOVE
|
||||||
{
|
| winuser::SWP_NOSIZE
|
||||||
Some(client_rect) => {
|
| winuser::SWP_FRAMECHANGED;
|
||||||
let (x, y, w, h) = (
|
|
||||||
client_rect.left,
|
// We generally don't want style changes here to affect window
|
||||||
client_rect.top,
|
// focus, but for fullscreen windows they must be activated
|
||||||
client_rect.right - client_rect.left,
|
// (i.e. focused) so that they appear on top of the taskbar
|
||||||
client_rect.bottom - client_rect.top,
|
if !new.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||||
);
|
flags |= winuser::SWP_NOACTIVATE;
|
||||||
winuser::SetWindowPos(
|
|
||||||
window,
|
|
||||||
ptr::null_mut(),
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
winuser::SWP_NOZORDER
|
|
||||||
| winuser::SWP_FRAMECHANGED
|
|
||||||
| winuser::SWP_NOACTIVATE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Refresh the window frame.
|
|
||||||
winuser::SetWindowPos(
|
|
||||||
window,
|
|
||||||
ptr::null_mut(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
winuser::SWP_NOZORDER
|
|
||||||
| winuser::SWP_NOMOVE
|
|
||||||
| winuser::SWP_NOSIZE
|
|
||||||
| winuser::SWP_FRAMECHANGED
|
|
||||||
| winuser::SWP_NOACTIVATE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh the window frame
|
||||||
|
winuser::SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
|
||||||
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
|
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -345,10 +307,25 @@ impl CursorFlags {
|
||||||
let client_rect = util::get_client_rect(window)?;
|
let client_rect = util::get_client_rect(window)?;
|
||||||
|
|
||||||
if util::is_focused(window) {
|
if util::is_focused(window) {
|
||||||
if self.contains(CursorFlags::GRABBED) {
|
let cursor_clip = match self.contains(CursorFlags::GRABBED) {
|
||||||
util::set_cursor_clip(Some(client_rect))?;
|
true => Some(client_rect),
|
||||||
} else {
|
false => None,
|
||||||
util::set_cursor_clip(None)?;
|
};
|
||||||
|
|
||||||
|
let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom);
|
||||||
|
let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?);
|
||||||
|
let desktop_rect = rect_to_tuple(util::get_desktop_rect());
|
||||||
|
|
||||||
|
let active_cursor_clip = match desktop_rect == active_cursor_clip {
|
||||||
|
true => None,
|
||||||
|
false => Some(active_cursor_clip),
|
||||||
|
};
|
||||||
|
|
||||||
|
// We do this check because calling `set_cursor_clip` incessantly will flood the event
|
||||||
|
// loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags`
|
||||||
|
// which at times gets called once every iteration of the eventloop.
|
||||||
|
if active_cursor_clip != cursor_clip.map(rect_to_tuple) {
|
||||||
|
util::set_cursor_clip(cursor_clip)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
100
src/window.rs
100
src/window.rs
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
error::{ExternalError, NotSupportedError, OsError},
|
error::{ExternalError, NotSupportedError, OsError},
|
||||||
event_loop::EventLoopWindowTarget,
|
event_loop::EventLoopWindowTarget,
|
||||||
monitor::{AvailableMonitorsIter, MonitorHandle},
|
monitor::{AvailableMonitorsIter, MonitorHandle, VideoMode},
|
||||||
platform_impl,
|
platform_impl,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,6 +45,18 @@ impl fmt::Debug for Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Window {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// If the window is in exclusive fullscreen, we must restore the desktop
|
||||||
|
// video mode (generally this would be done on application exit, but
|
||||||
|
// closing the window doesn't necessarily always mean application exit,
|
||||||
|
// such as when there are multiple windows)
|
||||||
|
if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() {
|
||||||
|
self.set_fullscreen(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Identifier of a window. Unique for each window.
|
/// Identifier of a window. Unique for each window.
|
||||||
///
|
///
|
||||||
/// Can be obtained with `window.id()`.
|
/// Can be obtained with `window.id()`.
|
||||||
|
@ -110,7 +122,7 @@ pub struct WindowAttributes {
|
||||||
/// Whether the window should be set as fullscreen upon creation.
|
/// Whether the window should be set as fullscreen upon creation.
|
||||||
///
|
///
|
||||||
/// The default is `None`.
|
/// The default is `None`.
|
||||||
pub fullscreen: Option<MonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
|
|
||||||
/// The title of the window in the title bar.
|
/// The title of the window in the title bar.
|
||||||
///
|
///
|
||||||
|
@ -168,10 +180,11 @@ impl Default for WindowAttributes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowBuilder {
|
impl WindowBuilder {
|
||||||
/// Initializes a new `WindowBuilder` with default values.
|
/// Initializes a new `WindowBuilder` with default values.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> WindowBuilder {
|
pub fn new() -> Self {
|
||||||
WindowBuilder {
|
WindowBuilder {
|
||||||
window: Default::default(),
|
window: Default::default(),
|
||||||
platform_specific: Default::default(),
|
platform_specific: Default::default(),
|
||||||
|
@ -180,21 +193,21 @@ impl WindowBuilder {
|
||||||
|
|
||||||
/// Requests the window to be of specific dimensions.
|
/// Requests the window to be of specific dimensions.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_inner_size(mut self, size: LogicalSize) -> WindowBuilder {
|
pub fn with_inner_size(mut self, size: LogicalSize) -> Self {
|
||||||
self.window.inner_size = Some(size);
|
self.window.inner_size = Some(size);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a minimum dimension size for the window
|
/// Sets a minimum dimension size for the window
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> WindowBuilder {
|
pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> Self {
|
||||||
self.window.min_inner_size = Some(min_size);
|
self.window.min_inner_size = Some(min_size);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a maximum dimension size for the window
|
/// Sets a maximum dimension size for the window
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> WindowBuilder {
|
pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> Self {
|
||||||
self.window.max_inner_size = Some(max_size);
|
self.window.max_inner_size = Some(max_size);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -210,57 +223,61 @@ impl WindowBuilder {
|
||||||
///
|
///
|
||||||
/// Due to a bug in XFCE, this has no effect on Xfwm.
|
/// Due to a bug in XFCE, this has no effect on Xfwm.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_resizable(mut self, resizable: bool) -> WindowBuilder {
|
pub fn with_resizable(mut self, resizable: bool) -> Self {
|
||||||
self.window.resizable = resizable;
|
self.window.resizable = resizable;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests a specific title for the window.
|
/// Requests a specific title for the window.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_title<T: Into<String>>(mut self, title: T) -> WindowBuilder {
|
pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
|
||||||
self.window.title = title.into();
|
self.window.title = title.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the window fullscreen state. None means a normal window, Some(MonitorHandle)
|
/// Sets the window fullscreen state. None means a normal window, Some(Fullscreen)
|
||||||
/// means a fullscreen window on that specific monitor
|
/// means a fullscreen window on that specific monitor
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_fullscreen(mut self, monitor: Option<MonitorHandle>) -> WindowBuilder {
|
pub fn with_fullscreen(mut self, monitor: Option<Fullscreen>) -> Self {
|
||||||
self.window.fullscreen = monitor;
|
self.window.fullscreen = monitor;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests maximized mode.
|
/// Requests maximized mode.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_maximized(mut self, maximized: bool) -> WindowBuilder {
|
pub fn with_maximized(mut self, maximized: bool) -> Self {
|
||||||
self.window.maximized = maximized;
|
self.window.maximized = maximized;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether the window will be initially hidden or visible.
|
/// Sets whether the window will be initially hidden or visible.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_visible(mut self, visible: bool) -> WindowBuilder {
|
pub fn with_visible(mut self, visible: bool) -> Self {
|
||||||
self.window.visible = visible;
|
self.window.visible = visible;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether the background of the window should be transparent.
|
/// Sets whether the background of the window should be transparent.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_transparent(mut self, transparent: bool) -> WindowBuilder {
|
pub fn with_transparent(mut self, transparent: bool) -> Self {
|
||||||
self.window.transparent = transparent;
|
self.window.transparent = transparent;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether the window should have a border, a title bar, etc.
|
/// Sets whether the window should have a border, a title bar, etc.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_decorations(mut self, decorations: bool) -> WindowBuilder {
|
pub fn with_decorations(mut self, decorations: bool) -> Self {
|
||||||
self.window.decorations = decorations;
|
self.window.decorations = decorations;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether or not the window will always be on top of other windows.
|
/// Sets whether or not the window will always be on top of other windows.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder {
|
pub fn with_always_on_top(mut self, always_on_top: bool) -> Self {
|
||||||
self.window.always_on_top = always_on_top;
|
self.window.always_on_top = always_on_top;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -278,7 +295,7 @@ impl WindowBuilder {
|
||||||
/// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That
|
/// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That
|
||||||
/// said, it's usually in the same ballpark as on Windows.
|
/// said, it's usually in the same ballpark as on Windows.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> WindowBuilder {
|
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> Self {
|
||||||
self.window.window_icon = window_icon;
|
self.window.window_icon = window_icon;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -291,7 +308,6 @@ impl WindowBuilder {
|
||||||
self,
|
self,
|
||||||
window_target: &EventLoopWindowTarget<T>,
|
window_target: &EventLoopWindowTarget<T>,
|
||||||
) -> Result<Window, OsError> {
|
) -> Result<Window, OsError> {
|
||||||
// building
|
|
||||||
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
|
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
|
||||||
.map(|window| Window { window })
|
.map(|window| Window { window })
|
||||||
}
|
}
|
||||||
|
@ -398,9 +414,8 @@ impl Window {
|
||||||
|
|
||||||
/// Modifies the position of the window.
|
/// Modifies the position of the window.
|
||||||
///
|
///
|
||||||
/// See `outer_position` for more information about the coordinates.
|
/// See `outer_position` for more information about the coordinates. This automatically un-maximizes the
|
||||||
///
|
/// window if it's maximized.
|
||||||
/// This is a no-op if the window has already been closed.
|
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
|
@ -430,7 +445,8 @@ impl Window {
|
||||||
|
|
||||||
/// Modifies the inner size of the window.
|
/// Modifies the inner size of the window.
|
||||||
///
|
///
|
||||||
/// See `inner_size` for more information about the values.
|
/// See `inner_size` for more information about the values. This automatically un-maximizes the
|
||||||
|
/// window if it's maximized.
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
|
@ -533,10 +549,27 @@ impl Window {
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
|
/// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a
|
||||||
|
/// video mode change. *Caveat!* macOS doesn't provide task switching (or
|
||||||
|
/// spaces!) while in exclusive fullscreen mode. This mode should be used
|
||||||
|
/// when a video mode change is desired, but for a better user experience,
|
||||||
|
/// borderless fullscreen might be preferred.
|
||||||
|
///
|
||||||
|
/// `Fullscreen::Borderless` provides a borderless fullscreen window on a
|
||||||
|
/// separate space. This is the idiomatic way for fullscreen games to work
|
||||||
|
/// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if
|
||||||
|
/// separate spaces are not preferred.
|
||||||
|
///
|
||||||
|
/// The dock and the menu bar are always disabled in fullscreen mode.
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
/// - **iOS:** Can only be called on the main thread.
|
||||||
|
/// - **Wayland:** Does not support exclusive fullscreen mode.
|
||||||
|
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
||||||
|
///
|
||||||
|
/// [simple]:
|
||||||
|
/// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
self.window.set_fullscreen(monitor)
|
self.window.set_fullscreen(fullscreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the window's current fullscreen state.
|
/// Gets the window's current fullscreen state.
|
||||||
|
@ -545,7 +578,7 @@ impl Window {
|
||||||
///
|
///
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
/// - **iOS:** Can only be called on the main thread.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<MonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
self.window.fullscreen()
|
self.window.fullscreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,10 +586,7 @@ impl Window {
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden
|
/// - **iOS:** Has no effect.
|
||||||
/// via [`setPrefersStatusBarHidden`].
|
|
||||||
///
|
|
||||||
/// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_decorations(&self, decorations: bool) {
|
pub fn set_decorations(&self, decorations: bool) {
|
||||||
self.window.set_decorations(decorations)
|
self.window.set_decorations(decorations)
|
||||||
|
@ -692,6 +722,12 @@ impl Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
|
||||||
|
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
|
||||||
|
self.window.raw_window_handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes the appearance of the mouse cursor.
|
/// Describes the appearance of the mouse cursor.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -725,7 +761,9 @@ pub enum CursorIcon {
|
||||||
Alias,
|
Alias,
|
||||||
Copy,
|
Copy,
|
||||||
NoDrop,
|
NoDrop,
|
||||||
|
/// Indicates something can be grabbed.
|
||||||
Grab,
|
Grab,
|
||||||
|
/// Indicates something is grabbed.
|
||||||
Grabbing,
|
Grabbing,
|
||||||
AllScroll,
|
AllScroll,
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
|
@ -754,3 +792,9 @@ impl Default for CursorIcon {
|
||||||
CursorIcon::Default
|
CursorIcon::Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Fullscreen {
|
||||||
|
Exclusive(VideoMode),
|
||||||
|
Borderless(MonitorHandle),
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue