mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
70 Commits
v0.20.0-al
...
v0.20.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31110be396 | ||
|
|
604016d69d | ||
|
|
1aab328e2a | ||
|
|
1366dc326a | ||
|
|
8e73287646 | ||
|
|
7eed52a97a | ||
|
|
31ada5a052 | ||
|
|
30b4f8dc9f | ||
|
|
cf0b8babbd | ||
|
|
c0c22c8ff1 | ||
|
|
73cf10e4f3 | ||
|
|
8a1c5277eb | ||
|
|
1e4c176506 | ||
|
|
3c27e7d88f | ||
|
|
5bc3cf18d9 | ||
|
|
131e67ddc1 | ||
|
|
e5ba79db04 | ||
|
|
f4e9bf51db | ||
|
|
03f9e8fce0 | ||
|
|
4ae9900363 | ||
|
|
454d4190b7 | ||
|
|
a28b60578d | ||
|
|
5a206de620 | ||
|
|
b547531499 | ||
|
|
39e668ffb0 | ||
|
|
bd1ac6cb1e | ||
|
|
8567758156 | ||
|
|
e8e4d4ce66 | ||
|
|
44af4f4f52 | ||
|
|
7daf146801 | ||
|
|
17b8310517 | ||
|
|
5ca828d445 | ||
|
|
1ea29b4de0 | ||
|
|
b00cdadb5b | ||
|
|
5d0bc5f607 | ||
|
|
ce5cf97e17 | ||
|
|
3ee59696e5 | ||
|
|
f5c624bcd6 | ||
|
|
026b331ba5 | ||
|
|
93c36ccf78 | ||
|
|
c1f314ccdc | ||
|
|
53a89f28a0 | ||
|
|
f874d76289 | ||
|
|
28775be115 | ||
|
|
7d3ff3d2d9 | ||
|
|
4a5d639d74 | ||
|
|
74a7cf55ea | ||
|
|
9393b14b01 | ||
|
|
f8bd671073 | ||
|
|
2af753f307 | ||
|
|
5bf303fd26 | ||
|
|
e37e46b155 | ||
|
|
b8192ef6f6 | ||
|
|
23354cf1a5 | ||
|
|
dd38fab2f3 | ||
|
|
ac08601b40 | ||
|
|
34db2d7d4c | ||
|
|
0e20973bdb | ||
|
|
29e2481597 | ||
|
|
3555de114a | ||
|
|
dbe6a1bcdf | ||
|
|
a195ce8146 | ||
|
|
9dd15d00d8 | ||
|
|
2442305bb7 | ||
|
|
063648368d | ||
|
|
918b2efce7 | ||
|
|
2467a997f4 | ||
|
|
f457c6a0b8 | ||
|
|
1193cada46 | ||
|
|
b0e09b8ffe |
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,6 +1,7 @@
|
||||
- [ ] Tested on all platforms changed
|
||||
- [ ] Compilation warnings were addressed
|
||||
- [ ] `cargo fmt` has been run on this branch
|
||||
- [ ] 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
|
||||
- [ ] Created an example program if it would help users understand this functionality
|
||||
- [ ] Updated [feature matrix](https://github.com/tomaka/winit/blob/master/FEATURES.md), if new features were added or implemented
|
||||
- [ ] Created or updated an example program if it would help users understand this functionality
|
||||
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "deps/apk-builder"]
|
||||
path = deps/apk-builder
|
||||
url = https://github.com/tomaka/android-rs-glue
|
||||
url = https://github.com/rust-windowing/android-rs-glue
|
||||
|
||||
30
.travis.yml
30
.travis.yml
@@ -34,7 +34,7 @@ matrix:
|
||||
os: osx
|
||||
rust: stable
|
||||
|
||||
# iOS
|
||||
# iOS x86_64
|
||||
- env: TARGET=x86_64-apple-ios
|
||||
os: osx
|
||||
rust: nightly
|
||||
@@ -42,19 +42,35 @@ matrix:
|
||||
os: osx
|
||||
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:
|
||||
- rustup self update
|
||||
- rustup target add $TARGET; true
|
||||
- rustup install nightly
|
||||
- rustup component add rustfmt --toolchain nightly
|
||||
- rustup toolchain install stable
|
||||
- rustup component add rustfmt --toolchain stable
|
||||
|
||||
script:
|
||||
- cargo +nightly fmt --all -- --check
|
||||
- cargo +stable fmt --all -- --check
|
||||
- cargo build --target $TARGET --verbose
|
||||
- cargo build --target $TARGET --features serde --verbose
|
||||
# Running iOS apps on OSX requires the simulator so we skip that for now
|
||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi
|
||||
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi
|
||||
# Running iOS apps on macOS requires the Simulator so we skip that for now
|
||||
- if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --verbose; fi
|
||||
- if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --features serde --verbose; fi
|
||||
|
||||
after_success:
|
||||
- |
|
||||
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,6 +1,53 @@
|
||||
# Unreleased
|
||||
|
||||
# 0.20.0 Alpha 1
|
||||
# 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:]`.
|
||||
|
||||
# 0.20.0 Alpha 2 (2019-07-09)
|
||||
|
||||
- On X11, non-resizable windows now have maximize explicitly disabled.
|
||||
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`
|
||||
and `WindowEvent::HoveredFile`.
|
||||
- On Mac, implement `DeviceEvent::Button`.
|
||||
- Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`.
|
||||
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.
|
||||
- Revert the use of invisible surfaces in Wayland, which introduced graphical glitches with OpenGL (#835)
|
||||
- On X11, implement `_NET_WM_PING` to allow desktop environment to kill unresponsive programs.
|
||||
- On Windows, when a window is initially invisible, it won't take focus from the existing visible windows.
|
||||
- On Windows, fix multiple calls to `request_redraw` during `EventsCleared` sending multiple `RedrawRequested events.`
|
||||
- On Windows, fix edge case where `RedrawRequested` could be dispatched before input events in event loop iteration.
|
||||
- On Windows, fix timing issue that could cause events to be improperly dispatched after `RedrawRequested` but before `EventsCleared`.
|
||||
- On macOS, drop unused Metal dependency.
|
||||
- On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected.
|
||||
- On Windows, fix `with_maximized` not properly setting window size to entire window.
|
||||
- On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`.
|
||||
|
||||
# 0.20.0 Alpha 1 (2019-06-21)
|
||||
|
||||
- Changes below are considered **breaking**.
|
||||
- Change all occurrences of `EventsLoop` to `EventLoop`.
|
||||
@@ -53,6 +100,9 @@
|
||||
- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus.
|
||||
- On macOS, fix command key event left and right reverse.
|
||||
- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend.
|
||||
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
|
||||
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
|
||||
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
|
||||
- On Windows, fix icon not showing up in corner of window.
|
||||
- On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior.
|
||||
|
||||
@@ -71,9 +121,6 @@
|
||||
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
|
||||
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
|
||||
- On Windows, ignore the AltGr key when populating the `ModifersState` type.
|
||||
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
|
||||
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
|
||||
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
|
||||
|
||||
# Version 0.18.1 (2018-12-30)
|
||||
|
||||
|
||||
19
Cargo.toml
19
Cargo.toml
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.20.0-alpha1"
|
||||
version = "0.20.0-alpha3"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
keywords = ["windowing"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/tomaka/winit"
|
||||
repository = "https://github.com/rust-windowing/winit"
|
||||
documentation = "https://docs.rs/winit"
|
||||
categories = ["gui"]
|
||||
|
||||
@@ -20,6 +20,7 @@ libc = "0.2"
|
||||
log = "0.4"
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
derivative = "1.0.2"
|
||||
raw-window-handle = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.21"
|
||||
@@ -35,11 +36,15 @@ objc = "0.2.3"
|
||||
cocoa = "0.18.4"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.3"
|
||||
core-video-sys = "0.1.2"
|
||||
dispatch = "0.1.4"
|
||||
objc = "0.2.3"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
version = "0.1.3"
|
||||
default_features = false
|
||||
features = ["display_link"]
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
|
||||
bitflags = "1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||
@@ -69,9 +74,9 @@ features = [
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
|
||||
calloop = "0.4.2"
|
||||
smithay-client-toolkit = "0.6.1"
|
||||
smithay-client-toolkit = "0.6"
|
||||
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]
|
||||
version = "0.8"
|
||||
version = "0.9"
|
||||
|
||||
60
FEATURES.md
60
FEATURES.md
@@ -84,6 +84,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 toggle**: The windows created by winit can be switched to and from fullscreen after
|
||||
creation.
|
||||
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
||||
for fullscreen windows, and if applicable, captures the monitor for exclusive
|
||||
use by this application.
|
||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
||||
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
||||
@@ -100,6 +103,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 icon**: Changing the cursor icon, or hiding the cursor.
|
||||
- **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.
|
||||
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
|
||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||
@@ -129,6 +133,20 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* GTK Theme Variant
|
||||
* Base window size
|
||||
|
||||
### iOS
|
||||
* 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
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
|
||||
@@ -155,15 +173,16 @@ Legend:
|
||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❌ |
|
||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❌ |
|
||||
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❌ |
|
||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||
|
||||
### System information
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A** |
|
||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ |
|
||||
|
||||
### Input handling
|
||||
@@ -174,7 +193,8 @@ Legend:
|
||||
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ |
|
||||
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ |
|
||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|
||||
|Multitouch |❓ |❌ |✔️ |✔️ |❓ |❌ |❌ |
|
||||
|Touch pressure |❌ |❌ |❌ |❌ |❌ |✔️ |❌ |
|
||||
|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ |
|
||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ |
|
||||
@@ -187,25 +207,25 @@ Changes in the API that have been agreed upon but aren't implemented across all
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
||||
|Event Loop 2.0 ([#459]) |✔️ |❌ |❌ |✔️ |❌ |❌ |❌ |
|
||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❌ |
|
||||
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||
|
||||
### Completed API Reworks
|
||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|
||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
||||
|
||||
[#165]: https://github.com/tomaka/winit/issues/165
|
||||
[#219]: https://github.com/tomaka/winit/issues/219
|
||||
[#242]: https://github.com/tomaka/winit/issues/242
|
||||
[#306]: https://github.com/tomaka/winit/issues/306
|
||||
[#315]: https://github.com/tomaka/winit/issues/315
|
||||
[#319]: https://github.com/tomaka/winit/issues/319
|
||||
[#33]: https://github.com/tomaka/winit/issues/33
|
||||
[#459]: https://github.com/tomaka/winit/issues/459
|
||||
[#5]: https://github.com/tomaka/winit/issues/5
|
||||
[#63]: https://github.com/tomaka/winit/issues/63
|
||||
[#720]: https://github.com/tomaka/winit/issues/720
|
||||
[#721]: https://github.com/tomaka/winit/issues/721
|
||||
[#750]: https://github.com/tomaka/winit/issues/750
|
||||
[#804]: https://github.com/tomaka/winit/issues/804
|
||||
[#812]: https://github.com/tomaka/winit/issues/812
|
||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
||||
[#219]: https://github.com/rust-windowing/winit/issues/219
|
||||
[#242]: https://github.com/rust-windowing/winit/issues/242
|
||||
[#306]: https://github.com/rust-windowing/winit/issues/306
|
||||
[#315]: https://github.com/rust-windowing/winit/issues/315
|
||||
[#319]: https://github.com/rust-windowing/winit/issues/319
|
||||
[#33]: https://github.com/rust-windowing/winit/issues/33
|
||||
[#459]: https://github.com/rust-windowing/winit/issues/459
|
||||
[#5]: https://github.com/rust-windowing/winit/issues/5
|
||||
[#63]: https://github.com/rust-windowing/winit/issues/63
|
||||
[#720]: https://github.com/rust-windowing/winit/issues/720
|
||||
[#721]: https://github.com/rust-windowing/winit/issues/721
|
||||
[#750]: https://github.com/rust-windowing/winit/issues/750
|
||||
[#804]: https://github.com/rust-windowing/winit/issues/804
|
||||
[#812]: https://github.com/rust-windowing/winit/issues/812
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
# winit - Cross-platform window creation and management in Rust
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
[](https://travis-ci.org/rust-windowing/winit)
|
||||
[](https://ci.appveyor.com/project/Osspial/winit/branch/master)
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.20.0-alpha1"
|
||||
winit = "0.20.0-alpha3"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
|
||||
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
|
||||
|
||||
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
|
||||
|
||||
## Contact Us
|
||||
|
||||
Join us in any of these:
|
||||
|
||||
@@ -12,37 +12,35 @@ fn main() {
|
||||
|
||||
let mut cursor_idx = 0;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
||||
if cursor_idx < CURSORS.len() - 1 {
|
||||
cursor_idx += 1;
|
||||
} else {
|
||||
cursor_idx = 0;
|
||||
}
|
||||
},
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
},
|
||||
_ => (),
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
||||
if cursor_idx < CURSORS.len() - 1 {
|
||||
cursor_idx += 1;
|
||||
} else {
|
||||
cursor_idx = 0;
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
@@ -14,8 +14,8 @@ fn main() {
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
@@ -34,9 +34,18 @@ fn main() {
|
||||
H => window.set_cursor_visible(modifiers.shift),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {:?}", delta),
|
||||
DeviceEvent::Button { button, state } => match state {
|
||||
ElementState::Pressed => println!("mouse button {} pressed", button),
|
||||
ElementState::Released => println!("mouse button {} released", button),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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,122 +1,73 @@
|
||||
use std::io::{self, Write};
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
monitor::MonitorHandle,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::monitor::{MonitorHandle, VideoMode};
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut macos_use_simple_fullscreen = false;
|
||||
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||
stdout().flush().unwrap();
|
||||
|
||||
let monitor = {
|
||||
// On macOS there are two fullscreen modes "native" and "simple"
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
|
||||
io::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 mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
match num {
|
||||
2 => macos_use_simple_fullscreen = true,
|
||||
_ => {},
|
||||
}
|
||||
let fullscreen = Some(match num {
|
||||
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
|
||||
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
|
||||
_ => panic!("Please enter a valid number"),
|
||||
});
|
||||
|
||||
// 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 decorations = true;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(monitor)
|
||||
.with_fullscreen(fullscreen.clone())
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
match (virtual_code, state) {
|
||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
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);
|
||||
} else {
|
||||
window.set_fullscreen(Some(window.current_monitor()));
|
||||
}
|
||||
},
|
||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||
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) => {
|
||||
is_maximized = !is_maximized;
|
||||
window.set_maximized(is_maximized);
|
||||
},
|
||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
},
|
||||
_ => (),
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match (virtual_code, state) {
|
||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||
if window.fullscreen().is_some() {
|
||||
window.set_fullscreen(None);
|
||||
} else {
|
||||
window.set_fullscreen(fullscreen.clone());
|
||||
}
|
||||
},
|
||||
}
|
||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||
println!("window.fullscreen {:?}", window.fullscreen());
|
||||
}
|
||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||
is_maximized = !is_maximized;
|
||||
window.set_maximized(is_maximized);
|
||||
}
|
||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
||||
decorations = !decorations;
|
||||
window.set_decorations(decorations);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -128,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||
}
|
||||
|
||||
print!("Please write the number of the monitor to use: ");
|
||||
io::stdout().flush().unwrap();
|
||||
stdout().flush().unwrap();
|
||||
|
||||
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 monitor = event_loop
|
||||
.available_monitors()
|
||||
@@ -142,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ fn main() {
|
||||
// action from the user, this is generally where you'd handle cleanup before
|
||||
// closing the window. How to close the window is detailed in the handler for
|
||||
// the Y key.
|
||||
},
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
@@ -63,19 +63,19 @@ fn main() {
|
||||
// sent.
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
},
|
||||
}
|
||||
N => {
|
||||
if close_requested {
|
||||
println!("Your window will continue to stay by your side.");
|
||||
close_requested = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{CursorIcon, WindowBuilder},
|
||||
window::{CursorIcon, Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
@@ -19,11 +19,34 @@ fn main() {
|
||||
.with_inner_size(WINDOW_SIZE.into())
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
|
||||
let mut video_mode_id = 0usize;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
window_senders.insert(window.id(), tx);
|
||||
thread::spawn(move || {
|
||||
while let Ok(event) = rx.recv() {
|
||||
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 {
|
||||
input:
|
||||
KeyboardInput {
|
||||
@@ -39,19 +62,32 @@ fn main() {
|
||||
use self::VirtualKeyCode::*;
|
||||
match key {
|
||||
A => window.set_always_on_top(state),
|
||||
C => {
|
||||
window.set_cursor_icon(match state {
|
||||
true => CursorIcon::Progress,
|
||||
false => CursorIcon::Default,
|
||||
})
|
||||
},
|
||||
C => window.set_cursor_icon(match state {
|
||||
true => CursorIcon::Progress,
|
||||
false => CursorIcon::Default,
|
||||
}),
|
||||
D => window.set_decorations(!state),
|
||||
F => {
|
||||
window.set_fullscreen(match state {
|
||||
true => Some(window.current_monitor()),
|
||||
false => None,
|
||||
})
|
||||
},
|
||||
// Cycle through video modes
|
||||
Right | Left => {
|
||||
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(),
|
||||
H => window.set_cursor_visible(!state),
|
||||
I => {
|
||||
@@ -60,49 +96,42 @@ fn main() {
|
||||
println!("-> inner_position : {:?}", window.inner_position());
|
||||
println!("-> outer_size : {:?}", window.outer_size());
|
||||
println!("-> inner_size : {:?}", window.inner_size());
|
||||
},
|
||||
L => {
|
||||
window.set_min_inner_size(match state {
|
||||
true => Some(WINDOW_SIZE.into()),
|
||||
false => None,
|
||||
})
|
||||
},
|
||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||
}
|
||||
L => window.set_min_inner_size(match state {
|
||||
true => Some(WINDOW_SIZE.into()),
|
||||
false => None,
|
||||
}),
|
||||
M => window.set_maximized(state),
|
||||
P => {
|
||||
window.set_outer_position({
|
||||
let mut position = window.outer_position().unwrap();
|
||||
let sign = if state { 1.0 } else { -1.0 };
|
||||
position.x += 10.0 * sign;
|
||||
position.y += 10.0 * sign;
|
||||
position
|
||||
})
|
||||
},
|
||||
P => window.set_outer_position({
|
||||
let mut position = window.outer_position().unwrap();
|
||||
let sign = if state { 1.0 } else { -1.0 };
|
||||
position.x += 10.0 * sign;
|
||||
position.y += 10.0 * sign;
|
||||
position
|
||||
}),
|
||||
Q => window.request_redraw(),
|
||||
R => window.set_resizable(state),
|
||||
S => {
|
||||
window.set_inner_size(
|
||||
match state {
|
||||
true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100),
|
||||
false => WINDOW_SIZE,
|
||||
}
|
||||
.into(),
|
||||
S => window.set_inner_size(
|
||||
match state {
|
||||
true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100),
|
||||
false => WINDOW_SIZE,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
W => window
|
||||
.set_cursor_position(
|
||||
(WINDOW_SIZE.0 as i32 / 2, WINDOW_SIZE.1 as i32 / 2).into(),
|
||||
)
|
||||
},
|
||||
W => {
|
||||
window
|
||||
.set_cursor_position(
|
||||
(WINDOW_SIZE.0 as i32 / 2, WINDOW_SIZE.1 as i32 / 2).into(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
.unwrap(),
|
||||
Z => {
|
||||
window.set_visible(false);
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
window.set_visible(true);
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -114,25 +143,24 @@ fn main() {
|
||||
false => ControlFlow::Exit,
|
||||
};
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested
|
||||
| WindowEvent::Destroyed
|
||||
| WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
window_senders.remove(&window_id);
|
||||
},
|
||||
_ => {
|
||||
if let Some(tx) = window_senders.get(&window_id) {
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
},
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested
|
||||
| WindowEvent::Destroyed
|
||||
| WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
window_senders.remove(&window_id);
|
||||
}
|
||||
_ => {
|
||||
if let Some(tx) = window_senders.get(&window_id) {
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
|
||||
@@ -28,7 +28,7 @@ fn main() {
|
||||
if windows.is_empty() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
},
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
@@ -39,10 +39,10 @@ fn main() {
|
||||
} => {
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
windows.insert(window.id(), window);
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
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,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -14,19 +14,21 @@ fn main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::EventsCleared => {
|
||||
window.request_redraw();
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0))
|
||||
},
|
||||
_ => (),
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::EventsCleared => {
|
||||
window.request_redraw();
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0))
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
println!("{:?}", event);
|
||||
}
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,24 +19,22 @@ fn main() {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
resizable = !resizable;
|
||||
println!("Resizable: {}", resizable);
|
||||
window.set_resizable(resizable);
|
||||
},
|
||||
_ => (),
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
||||
state: ElementState::Released,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
resizable = !resizable;
|
||||
println!("Resizable: {}", resizable);
|
||||
window.set_resizable(resizable);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
|
||||
@@ -21,11 +21,11 @@ fn main() {
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length)
|
||||
},
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
|
||||
println!("\nTimer\n");
|
||||
},
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
|
||||
@@ -7,6 +7,6 @@ fn main() {
|
||||
println!("Listing available video modes:");
|
||||
|
||||
for mode in monitor.video_modes() {
|
||||
println!("{:?}", mode);
|
||||
println!("{}", mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,7 @@ fn main() {
|
||||
// you'll be bitten by the low-quality downscaling built into the WM.
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
|
||||
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let image = image::open(path).expect("Failed to open icon path");
|
||||
use image::{GenericImageView, Pixel};
|
||||
let (width, height) = image.dimensions();
|
||||
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
|
||||
for (_, _, pixel) in image.pixels() {
|
||||
rgba.extend_from_slice(&pixel.to_rgba().data);
|
||||
}
|
||||
(rgba, width, height)
|
||||
};
|
||||
let icon = Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon");
|
||||
let icon = load_icon(Path::new(path));
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
@@ -43,7 +33,7 @@ fn main() {
|
||||
CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
DroppedFile(path) => {
|
||||
window.set_window_icon(Some(load_icon(&path)));
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
platform::desktop::EventLoopExtDesktop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
// 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"
|
||||
))]
|
||||
fn main() {
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
platform::desktop::EventLoopExtDesktop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
@@ -14,14 +24,12 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
println!("Close the window to continue.");
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
event_loop.run_return(|event, _, control_flow| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
});
|
||||
drop(window);
|
||||
|
||||
@@ -31,15 +39,18 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
println!("Wa ha ha! You thought that closing the window would finish this?!");
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
event_loop.run_return(|event, _, control_flow| match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
});
|
||||
|
||||
println!("Okay we're done now for real.");
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "android", target_os = "emscripten"))]
|
||||
fn main() {
|
||||
println!("This platform doesn't support run_return.");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
merge_imports=true
|
||||
match_block_trailing_comma=true
|
||||
force_explicit_abi=true
|
||||
format_macro_matchers=true
|
||||
use_field_init_shorthand=true
|
||||
format_code_in_doc_comments=true
|
||||
force_multiline_blocks=true
|
||||
# merge_imports=true
|
||||
|
||||
18
src/error.rs
18
src/error.rs
@@ -48,8 +48,8 @@ macro_rules! os_error {
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
formatter.pad(&format!(
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad(&format!(
|
||||
"os error at {}:{}: {}",
|
||||
self.file, self.line, self.error
|
||||
))
|
||||
@@ -57,23 +57,23 @@ impl fmt::Display for OsError {
|
||||
}
|
||||
|
||||
impl fmt::Display for ExternalError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
ExternalError::NotSupported(e) => e.fmt(formatter),
|
||||
ExternalError::Os(e) => e.fmt(formatter),
|
||||
ExternalError::NotSupported(e) => e.fmt(f),
|
||||
ExternalError::Os(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NotSupportedError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
formatter.debug_struct("NotSupportedError").finish()
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("NotSupportedError").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NotSupportedError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
formatter.pad("the requested operation is not supported by Winit")
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.pad("the requested operation is not supported by Winit")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
102
src/event.rs
102
src/event.rs
@@ -37,10 +37,11 @@ pub enum Event<T> {
|
||||
/// emitted, it is guaranteed to be the last event emitted.
|
||||
LoopDestroyed,
|
||||
|
||||
/// Emitted when the application has been suspended or resumed.
|
||||
///
|
||||
/// The parameter is true if app was suspended, and false if it has been resumed.
|
||||
Suspended(bool),
|
||||
/// Emitted when the application has been suspended.
|
||||
Suspended,
|
||||
|
||||
/// Emitted when the application has been resumed.
|
||||
Resumed,
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
@@ -53,7 +54,8 @@ impl<T> Event<T> {
|
||||
NewEvents(cause) => Ok(NewEvents(cause)),
|
||||
EventsCleared => Ok(EventsCleared),
|
||||
LoopDestroyed => Ok(LoopDestroyed),
|
||||
Suspended(suspended) => Ok(Suspended(suspended)),
|
||||
Suspended => Ok(Suspended),
|
||||
Resumed => Ok(Resumed),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +198,7 @@ pub enum WindowEvent {
|
||||
/// * Changing the display's DPI factor (e.g. in Control Panel on Windows).
|
||||
/// * Moving the window to a display with a different DPI factor.
|
||||
///
|
||||
/// For more information about DPI in general, see the [`dpi`](dpi/index.html) module.
|
||||
/// For more information about DPI in general, see the [`dpi`](../dpi/index.html) module.
|
||||
HiDpiFactorChanged(f64),
|
||||
}
|
||||
|
||||
@@ -301,30 +303,94 @@ pub enum TouchPhase {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// Represents touch event
|
||||
/// Represents a touch event
|
||||
///
|
||||
/// Every time user touches screen new Start event with some finger id is generated.
|
||||
/// When the finger is removed from the screen End event with same id is generated.
|
||||
/// Every time the user touches the screen, a new `Start` event with an unique
|
||||
/// 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).
|
||||
/// There may be 0 or more Move events.
|
||||
/// After a `Start` event has been emitted, there may be zero or more `Move`
|
||||
/// 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.
|
||||
///
|
||||
/// Gesture regonizer using this event should assume that Start event received with same id
|
||||
/// 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.
|
||||
/// 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
|
||||
/// device against their face.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Touch {
|
||||
pub device_id: DeviceId,
|
||||
pub phase: TouchPhase,
|
||||
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**.
|
||||
pub force: Option<Force>,
|
||||
/// Unique identifier of a finger.
|
||||
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.
|
||||
pub type ScanCode = u32;
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ use crate::{
|
||||
///
|
||||
/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()`
|
||||
/// initializes everything that will be required to create windows. For example on Linux creating
|
||||
/// an events loop opens a connection to the X or Wayland server.
|
||||
/// an event loop opens a connection to the X or Wayland server.
|
||||
///
|
||||
/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs.
|
||||
///
|
||||
/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic
|
||||
/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
|
||||
/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the
|
||||
/// `EventLoopProxy` allows you to wake up an `EventLoop` from an other thread.
|
||||
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
|
||||
pub struct EventLoop<T: 'static> {
|
||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
@@ -37,23 +37,24 @@ pub struct EventLoop<T: 'static> {
|
||||
|
||||
/// Target that associates windows with an `EventLoop`.
|
||||
///
|
||||
/// This type exists to allow you to create new windows while Winit executes your callback.
|
||||
/// `EventLoop` will coerce into this type, so functions that take this as a parameter can also
|
||||
/// take `&EventLoop`.
|
||||
/// This type exists to allow you to create new windows while Winit executes
|
||||
/// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
|
||||
/// EventLoop<T>`), so functions that take this as a parameter can also take
|
||||
/// `&EventLoop`.
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
||||
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoop<T> {
|
||||
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmtr.pad("EventLoop { .. }")
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoop { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
||||
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmtr.pad("EventLoopWindowTarget { .. }")
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoopWindowTarget { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ impl EventLoop<()> {
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn new() -> EventLoop<()> {
|
||||
EventLoop::<()>::new_user_event()
|
||||
EventLoop::<()>::with_user_event()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,14 +115,14 @@ impl<T> EventLoop<T> {
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn new_user_event() -> EventLoop<T> {
|
||||
pub fn with_user_event() -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Hijacks the calling thread and initializes the `winit` event loop with the provided
|
||||
/// Hijacks the calling thread and initializes the winit event loop with the provided
|
||||
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
|
||||
/// access any data from the calling context.
|
||||
///
|
||||
@@ -172,11 +173,18 @@ impl<T> Deref for EventLoop<T> {
|
||||
}
|
||||
|
||||
/// Used to send custom events to `EventLoop`.
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
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> {
|
||||
/// 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
|
||||
@@ -189,8 +197,8 @@ impl<T: 'static> EventLoopProxy<T> {
|
||||
}
|
||||
|
||||
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
||||
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmtr.pad("EventLoopProxy { .. }")
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("EventLoopProxy { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ pub enum BadIcon {
|
||||
}
|
||||
|
||||
impl fmt::Display for BadIcon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let msg = match self {
|
||||
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
|
||||
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
@@ -44,7 +44,7 @@ impl fmt::Display for BadIcon {
|
||||
width, height, pixel_count, width_x_height,
|
||||
),
|
||||
};
|
||||
write!(formatter, "{}", msg)
|
||||
write!(f, "{}", msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
51
src/lib.rs
51
src/lib.rs
@@ -22,11 +22,15 @@
|
||||
//!
|
||||
//! # Event handling
|
||||
//!
|
||||
//! Once a [`Window`] has been created, it will *generate events*. For example whenever the user moves
|
||||
//! the [`Window`], resizes the [`Window`], moves the mouse, etc. an event is generated.
|
||||
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
|
||||
//! generate a [`WindowEvent`] when certain things happen, like whenever the user moves their mouse
|
||||
//! or presses a key inside the [`Window`]. Devices can generate a [`DeviceEvent`] directly as well,
|
||||
//! which contains unfiltered event data that isn't specific to a certain window. Some user
|
||||
//! activity, like mouse movement, can generate both a [`WindowEvent`] *and* a [`DeviceEvent`]. You
|
||||
//! can also create and handle your own custom [`UserEvent`]s, if desired.
|
||||
//!
|
||||
//! The events generated by a [`Window`] can be retreived from the [`EventLoop`] the [`Window`] was created
|
||||
//! with.
|
||||
//! Events can be retreived by using an [`EventLoop`]. A [`Window`] will send its events to the
|
||||
//! [`EventLoop`] object it was created with.
|
||||
//!
|
||||
//! You do this by calling [`event_loop.run(...)`][event_loop_run]. This function will run forever
|
||||
//! unless `control_flow` is set to [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`]
|
||||
@@ -35,13 +39,31 @@
|
||||
//! ```no_run
|
||||
//! use winit::{
|
||||
//! event::{Event, WindowEvent},
|
||||
//! event_loop::ControlFlow,
|
||||
//! event_loop::{ControlFlow, EventLoop},
|
||||
//! window::WindowBuilder,
|
||||
//! };
|
||||
//! # use winit::event_loop::EventLoop;
|
||||
//! # let event_loop = EventLoop::new();
|
||||
//!
|
||||
//! let event_loop = EventLoop::new();
|
||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
//!
|
||||
//! event_loop.run(move |event, _, control_flow| {
|
||||
//! match event {
|
||||
//! Event::EventsCleared => {
|
||||
//! // Application update code.
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::RedrawRequested,
|
||||
//! ..
|
||||
//! } => {
|
||||
//! // Redraw the application.
|
||||
//! //
|
||||
//! // It's preferrable to render in this event rather than in EventsCleared, since
|
||||
//! // rendering in here allows the program to gracefully handle redraws requested
|
||||
//! // by the OS.
|
||||
//! },
|
||||
//! Event::WindowEvent {
|
||||
//! event: WindowEvent::CloseRequested,
|
||||
//! ..
|
||||
@@ -49,7 +71,13 @@
|
||||
//! println!("The close button was pressed; stopping");
|
||||
//! *control_flow = ControlFlow::Exit
|
||||
//! },
|
||||
//! _ => *control_flow = ControlFlow::Wait,
|
||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
//! // dispatched any events. This is ideal for games and similar applications.
|
||||
//! _ => *control_flow = ControlFlow::Poll,
|
||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
||||
//! // This is ideal for non-game applications that only update in response to user
|
||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||
//! // _ => *control_flow = ControlFlow::Wait,
|
||||
//! }
|
||||
//! });
|
||||
//! ```
|
||||
@@ -76,7 +104,9 @@
|
||||
//! [window_builder_build]: ./window/struct.WindowBuilder.html#method.build
|
||||
//! [window_id_fn]: ./window/struct.Window.html#method.id
|
||||
//! [`Event`]: ./event/enum.Event.html
|
||||
//! [`WindowEvent`]: ./event/enum.Event.html#variant.WindowEvent
|
||||
//! [`WindowEvent`]: ./event/enum.WindowEvent.html
|
||||
//! [`DeviceEvent`]: ./event/enum.DeviceEvent.html
|
||||
//! [`UserEvent`]: ./event/enum.Event.html#variant.UserEvent
|
||||
//! [`LoopDestroyed`]: ./event/enum.Event.html#variant.LoopDestroyed
|
||||
//! [`platform`]: ./platform/index.html
|
||||
|
||||
@@ -91,10 +121,9 @@ extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate derivative;
|
||||
#[macro_use]
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "ios", target_os = "windows"))]
|
||||
extern crate bitflags;
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[macro_use]
|
||||
|
||||
@@ -52,17 +52,41 @@ impl Iterator for AvailableMonitorsIter {
|
||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||
///
|
||||
/// [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(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
pub(crate) video_mode: platform_impl::VideoMode,
|
||||
}
|
||||
|
||||
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 {
|
||||
/// Returns the resolution of this video mode.
|
||||
#[inline]
|
||||
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
|
||||
@@ -73,15 +97,37 @@ impl VideoMode {
|
||||
///
|
||||
/// - **Wayland:** Always returns 32.
|
||||
/// - **iOS:** Always returns 32.
|
||||
#[inline]
|
||||
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
|
||||
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
||||
/// value to be exact.
|
||||
#[inline]
|
||||
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.
|
||||
///
|
||||
/// [`Window`]: ../window/struct.Window.html
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
pub(crate) inner: platform_impl::MonitorHandle,
|
||||
}
|
||||
@@ -119,7 +165,7 @@ impl MonitorHandle {
|
||||
|
||||
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
///
|
||||
/// See the [`dpi`](dpi/index.html) module for more information.
|
||||
/// See the [`dpi`](../dpi/index.html) module for more information.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
|
||||
@@ -4,13 +4,13 @@ use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
event_loop::EventLoop,
|
||||
monitor::MonitorHandle,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to iOS.
|
||||
/// Additional methods on [`EventLoop`] that are specific to iOS.
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -20,32 +20,76 @@ 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 {
|
||||
/// 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// 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);
|
||||
|
||||
/// 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).
|
||||
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).
|
||||
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 {
|
||||
@@ -73,23 +117,75 @@ impl WindowExtIOS for Window {
|
||||
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
||||
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 {
|
||||
/// 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;
|
||||
|
||||
/// 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
|
||||
/// 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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).
|
||||
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).
|
||||
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 {
|
||||
@@ -110,12 +206,41 @@ impl WindowBuilderExtIOS for WindowBuilder {
|
||||
self.platform_specific.valid_orientations = valid_orientations;
|
||||
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 {
|
||||
/// 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;
|
||||
|
||||
/// 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 {
|
||||
@@ -123,9 +248,14 @@ impl MonitorHandleExtIOS for MonitorHandle {
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
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)]
|
||||
pub enum ValidOrientations {
|
||||
/// Excludes `PortraitUpsideDown` on iphone
|
||||
@@ -161,3 +291,19 @@ pub enum Idiom {
|
||||
TV,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,26 @@ use crate::{
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Corresponds to `NSRequestUserAttentionType`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum RequestUserAttentionType {
|
||||
/// Corresponds to `NSCriticalRequest`.
|
||||
///
|
||||
/// Dock icon will bounce until the application is focused.
|
||||
Critical,
|
||||
|
||||
/// Corresponds to `NSInformationalRequest`.
|
||||
///
|
||||
/// Dock icon will bounce once.
|
||||
Informational,
|
||||
}
|
||||
|
||||
impl Default for RequestUserAttentionType {
|
||||
fn default() -> Self {
|
||||
RequestUserAttentionType::Critical
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to MacOS.
|
||||
pub trait WindowExtMacOS {
|
||||
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
|
||||
@@ -22,11 +42,7 @@ pub trait WindowExtMacOS {
|
||||
|
||||
/// Request user attention, causing the application's dock icon to bounce.
|
||||
/// Note that this has no effect if the application is already focused.
|
||||
///
|
||||
/// The `is_critical` flag has the following effects:
|
||||
/// - `false`: the dock icon will only bounce once.
|
||||
/// - `true`: the dock icon will bounce until the application is focused.
|
||||
fn request_user_attention(&self, is_critical: bool);
|
||||
fn request_user_attention(&self, request_type: RequestUserAttentionType);
|
||||
|
||||
/// Returns whether or not the window is in simple fullscreen mode.
|
||||
fn simple_fullscreen(&self) -> bool;
|
||||
@@ -53,8 +69,8 @@ impl WindowExtMacOS for Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn request_user_attention(&self, is_critical: bool) {
|
||||
self.window.request_user_attention(is_critical)
|
||||
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
|
||||
self.window.request_user_attention(request_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -113,6 +129,7 @@ pub trait WindowBuilderExtMacOS {
|
||||
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
|
||||
/// Build window with `resizeIncrements` property. Values must not be 0.
|
||||
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
|
||||
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
@@ -166,6 +183,12 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
self.platform_specific.resize_increments = Some(increments.into());
|
||||
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.
|
||||
|
||||
@@ -6,14 +6,15 @@ use smithay_client_toolkit::window::{ButtonState, Theme};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::EventLoop,
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
use crate::platform_impl::{
|
||||
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
|
||||
@@ -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.
|
||||
pub trait EventLoopExtUnix {
|
||||
/// Builds a new `EventLoops` that is forced to use X11.
|
||||
@@ -101,32 +153,14 @@ pub trait EventLoopExtUnix {
|
||||
fn new_wayland() -> Self
|
||||
where
|
||||
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> {
|
||||
#[inline]
|
||||
fn new_x11() -> Result<Self, XNotSupported> {
|
||||
LinuxEventLoop::new_x11().map(|ev| {
|
||||
EventLoop {
|
||||
event_loop: ev,
|
||||
_marker: ::std::marker::PhantomData,
|
||||
}
|
||||
LinuxEventLoop::new_x11().map(|ev| EventLoop {
|
||||
event_loop: ev,
|
||||
_marker: ::std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,33 +174,6 @@ impl<T> EventLoopExtUnix for EventLoop<T> {
|
||||
_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.
|
||||
@@ -294,7 +301,7 @@ impl WindowExtUnix for Window {
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme) {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,25 +75,26 @@ impl EventLoop {
|
||||
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
||||
},
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: motion.pointer_id as u64,
|
||||
device_id: DEVICE_ID,
|
||||
}),
|
||||
})
|
||||
},
|
||||
}
|
||||
android_glue::Event::InitWindow => {
|
||||
// The activity went to foreground.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(false);
|
||||
}
|
||||
Some(Event::Suspended(false))
|
||||
},
|
||||
Some(Event::Resumed)
|
||||
}
|
||||
android_glue::Event::TermWindow => {
|
||||
// The activity went to background.
|
||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
||||
(*cb)(true);
|
||||
}
|
||||
Some(Event::Suspended(true))
|
||||
},
|
||||
Some(Event::Suspended)
|
||||
}
|
||||
android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => {
|
||||
// Activity Orientation changed or resized.
|
||||
let native_window = unsafe { android_glue::native_window() };
|
||||
@@ -108,14 +109,14 @@ impl EventLoop {
|
||||
event: WindowEvent::Resized(size),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
android_glue::Event::WindowRedrawNeeded => {
|
||||
// The activity needs to be redrawn.
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Redraw,
|
||||
})
|
||||
},
|
||||
}
|
||||
android_glue::Event::Wake => Some(Event::Awakened),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -251,7 +251,7 @@ extern "C" fn mouse_callback(
|
||||
delta: ((*event).movementX as f64, (*event).movementY as f64),
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
mouse_input @ ffi::EMSCRIPTEN_EVENT_MOUSEDOWN
|
||||
| mouse_input @ ffi::EMSCRIPTEN_EVENT_MOUSEUP => {
|
||||
let button = match (*event).button {
|
||||
@@ -274,8 +274,8 @@ extern "C" fn mouse_callback(
|
||||
modifiers,
|
||||
},
|
||||
})
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ffi::EM_FALSE
|
||||
@@ -310,7 +310,7 @@ extern "C" fn keyboard_callback(
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
ffi::EMSCRIPTEN_EVENT_KEYUP => {
|
||||
queue.lock().unwrap().push_back(::Event::WindowEvent {
|
||||
window_id: ::WindowId(WindowId(0)),
|
||||
@@ -324,8 +324,8 @@ extern "C" fn keyboard_callback(
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ffi::EM_FALSE
|
||||
@@ -362,6 +362,7 @@ extern "C" fn touch_callback(
|
||||
phase,
|
||||
id: touch.identifier as u64,
|
||||
location,
|
||||
force: None, // TODO
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -757,7 +758,7 @@ fn error_to_str(code: ffi::EMSCRIPTEN_RESULT) -> &'static str {
|
||||
match code {
|
||||
ffi::EMSCRIPTEN_RESULT_SUCCESS | ffi::EMSCRIPTEN_RESULT_DEFERRED => {
|
||||
"Internal error in the library (success detected as failure)"
|
||||
},
|
||||
}
|
||||
|
||||
ffi::EMSCRIPTEN_RESULT_NOT_SUPPORTED => "Not supported",
|
||||
ffi::EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED => "Failed not deferred",
|
||||
@@ -778,7 +779,7 @@ fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) ->
|
||||
Ok(key) => key,
|
||||
Err(_) => {
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
};
|
||||
if key.chars().count() == 1 {
|
||||
key.as_bytes()[0]
|
||||
@@ -797,25 +798,21 @@ fn key_translate_virt(
|
||||
Ok(key) => key,
|
||||
Err(_) => {
|
||||
return None;
|
||||
},
|
||||
}
|
||||
};
|
||||
use VirtualKeyCode::*;
|
||||
match key {
|
||||
"Alt" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_LEFT => Some(LAlt),
|
||||
ffi::DOM_KEY_LOCATION_RIGHT => Some(RAlt),
|
||||
_ => None,
|
||||
}
|
||||
"Alt" => match location {
|
||||
ffi::DOM_KEY_LOCATION_LEFT => Some(LAlt),
|
||||
ffi::DOM_KEY_LOCATION_RIGHT => Some(RAlt),
|
||||
_ => None,
|
||||
},
|
||||
"AltGraph" => None,
|
||||
"CapsLock" => None,
|
||||
"Control" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_LEFT => Some(LControl),
|
||||
ffi::DOM_KEY_LOCATION_RIGHT => Some(RControl),
|
||||
_ => None,
|
||||
}
|
||||
"Control" => match location {
|
||||
ffi::DOM_KEY_LOCATION_LEFT => Some(LControl),
|
||||
ffi::DOM_KEY_LOCATION_RIGHT => Some(RControl),
|
||||
_ => None,
|
||||
},
|
||||
"Fn" => None,
|
||||
"FnLock" => None,
|
||||
@@ -823,22 +820,18 @@ fn key_translate_virt(
|
||||
"Meta" => None,
|
||||
"NumLock" => Some(Numlock),
|
||||
"ScrollLock" => Some(Scroll),
|
||||
"Shift" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_LEFT => Some(LShift),
|
||||
ffi::DOM_KEY_LOCATION_RIGHT => Some(RShift),
|
||||
_ => None,
|
||||
}
|
||||
"Shift" => match location {
|
||||
ffi::DOM_KEY_LOCATION_LEFT => Some(LShift),
|
||||
ffi::DOM_KEY_LOCATION_RIGHT => Some(RShift),
|
||||
_ => None,
|
||||
},
|
||||
"Super" => None,
|
||||
"Symbol" => None,
|
||||
"SymbolLock" => None,
|
||||
|
||||
"Enter" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadEnter),
|
||||
_ => Some(Return),
|
||||
}
|
||||
"Enter" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadEnter),
|
||||
_ => Some(Return),
|
||||
},
|
||||
"Tab" => Some(Tab),
|
||||
" " => Some(Space),
|
||||
@@ -1163,65 +1156,45 @@ fn key_translate_virt(
|
||||
"Divide" => Some(Divide),
|
||||
"Subtract" | "-" => Some(Subtract),
|
||||
"Separator" => None,
|
||||
"0" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad0),
|
||||
_ => Some(Key0),
|
||||
}
|
||||
"0" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad0),
|
||||
_ => Some(Key0),
|
||||
},
|
||||
"1" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad1),
|
||||
_ => Some(Key1),
|
||||
}
|
||||
"1" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad1),
|
||||
_ => Some(Key1),
|
||||
},
|
||||
"2" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad2),
|
||||
_ => Some(Key2),
|
||||
}
|
||||
"2" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad2),
|
||||
_ => Some(Key2),
|
||||
},
|
||||
"3" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad3),
|
||||
_ => Some(Key3),
|
||||
}
|
||||
"3" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad3),
|
||||
_ => Some(Key3),
|
||||
},
|
||||
"4" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad4),
|
||||
_ => Some(Key4),
|
||||
}
|
||||
"4" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad4),
|
||||
_ => Some(Key4),
|
||||
},
|
||||
"5" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad5),
|
||||
_ => Some(Key5),
|
||||
}
|
||||
"5" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad5),
|
||||
_ => Some(Key5),
|
||||
},
|
||||
"6" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad6),
|
||||
_ => Some(Key6),
|
||||
}
|
||||
"6" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad6),
|
||||
_ => Some(Key6),
|
||||
},
|
||||
"7" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad7),
|
||||
_ => Some(Key7),
|
||||
}
|
||||
"7" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad7),
|
||||
_ => Some(Key7),
|
||||
},
|
||||
"8" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad8),
|
||||
_ => Some(Key8),
|
||||
}
|
||||
"8" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad8),
|
||||
_ => Some(Key8),
|
||||
},
|
||||
"9" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad9),
|
||||
_ => Some(Key9),
|
||||
}
|
||||
"9" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad9),
|
||||
_ => Some(Key9),
|
||||
},
|
||||
|
||||
"A" | "a" => Some(A),
|
||||
@@ -1254,17 +1227,13 @@ fn key_translate_virt(
|
||||
"'" => Some(Apostrophe),
|
||||
"\\" => Some(Backslash),
|
||||
":" => Some(Colon),
|
||||
"," => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadComma),
|
||||
_ => Some(Comma),
|
||||
}
|
||||
"," => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadComma),
|
||||
_ => Some(Comma),
|
||||
},
|
||||
"=" => {
|
||||
match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadEquals),
|
||||
_ => Some(Equals),
|
||||
}
|
||||
"=" => match location {
|
||||
ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadEquals),
|
||||
_ => Some(Equals),
|
||||
},
|
||||
"{" => Some(LBracket),
|
||||
"." => Some(Period),
|
||||
|
||||
@@ -71,7 +71,7 @@ impl Drop for AppStateImpl {
|
||||
let () = msg_send![window, release];
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,15 +128,13 @@ impl AppState {
|
||||
queued_windows.push(window);
|
||||
msg_send![window, retain];
|
||||
return;
|
||||
},
|
||||
&mut AppStateImpl::ProcessingEvents { .. } => {},
|
||||
&mut AppStateImpl::InUserCallback { .. } => {},
|
||||
&mut AppStateImpl::Terminated => {
|
||||
panic!(
|
||||
"Attempt to create a `Window` \
|
||||
after the app has terminated"
|
||||
)
|
||||
},
|
||||
}
|
||||
&mut AppStateImpl::ProcessingEvents { .. } => {}
|
||||
&mut AppStateImpl::InUserCallback { .. } => {}
|
||||
&mut AppStateImpl::Terminated => panic!(
|
||||
"Attempt to create a `Window` \
|
||||
after the app has terminated"
|
||||
),
|
||||
app_state => unreachable!("unexpected state: {:#?}", app_state), /* all other cases should be impossible */
|
||||
}
|
||||
drop(this);
|
||||
@@ -154,13 +152,11 @@ impl AppState {
|
||||
let windows = ptr::read(queued_windows);
|
||||
let events = ptr::read(queued_events);
|
||||
(windows, events)
|
||||
},
|
||||
_ => {
|
||||
panic!(
|
||||
"winit iOS expected the app to be in a `NotLaunched` \
|
||||
state, but was not - please file an issue"
|
||||
)
|
||||
},
|
||||
}
|
||||
_ => panic!(
|
||||
"winit iOS expected the app to be in a `NotLaunched` \
|
||||
state, but was not - please file an issue"
|
||||
),
|
||||
};
|
||||
ptr::write(
|
||||
&mut this.app_state,
|
||||
@@ -180,12 +176,10 @@ impl AppState {
|
||||
ref mut queued_windows,
|
||||
..
|
||||
} => mem::replace(queued_windows, Vec::new()),
|
||||
_ => {
|
||||
panic!(
|
||||
"winit iOS expected the app to be in a `Launching` \
|
||||
state, but was not - please file an issue"
|
||||
)
|
||||
},
|
||||
_ => panic!(
|
||||
"winit iOS expected the app to be in a `Launching` \
|
||||
state, but was not - please file an issue"
|
||||
),
|
||||
};
|
||||
// have to drop RefMut because the window setup code below can trigger new events
|
||||
drop(this);
|
||||
@@ -228,13 +222,11 @@ impl AppState {
|
||||
let events = ptr::read(queued_events);
|
||||
let event_handler = ptr::read(queued_event_handler);
|
||||
(windows, events, event_handler)
|
||||
},
|
||||
_ => {
|
||||
panic!(
|
||||
"winit iOS expected the app to be in a `Launching` \
|
||||
state, but was not - please file an issue"
|
||||
)
|
||||
},
|
||||
}
|
||||
_ => panic!(
|
||||
"winit iOS expected the app to be in a `Launching` \
|
||||
state, but was not - please file an issue"
|
||||
),
|
||||
};
|
||||
ptr::write(
|
||||
&mut this.app_state,
|
||||
@@ -283,7 +275,7 @@ impl AppState {
|
||||
},
|
||||
);
|
||||
Event::NewEvents(StartCause::Poll)
|
||||
},
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
let (event_handler, start) = match &mut this.app_state {
|
||||
&mut AppStateImpl::NotLaunched { .. }
|
||||
@@ -305,7 +297,7 @@ impl AppState {
|
||||
start,
|
||||
requested_resume: None,
|
||||
})
|
||||
},
|
||||
}
|
||||
ControlFlow::WaitUntil(requested_resume) => {
|
||||
let (event_handler, start) = match &mut this.app_state {
|
||||
&mut AppStateImpl::NotLaunched { .. }
|
||||
@@ -334,7 +326,7 @@ impl AppState {
|
||||
requested_resume: Some(requested_resume),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
ControlFlow::Exit => bug!("unexpected controlflow `Exit`"),
|
||||
};
|
||||
drop(this);
|
||||
@@ -365,7 +357,7 @@ impl AppState {
|
||||
} => {
|
||||
queued_events.extend(events);
|
||||
return;
|
||||
},
|
||||
}
|
||||
&mut AppStateImpl::ProcessingEvents {
|
||||
ref mut event_handler,
|
||||
ref mut active_control_flow,
|
||||
@@ -461,7 +453,7 @@ impl AppState {
|
||||
let mut this = AppState::get_mut();
|
||||
match &mut this.app_state {
|
||||
&mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return,
|
||||
&mut AppStateImpl::ProcessingEvents { .. } => {},
|
||||
&mut AppStateImpl::ProcessingEvents { .. } => {}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
drop(this);
|
||||
@@ -474,25 +466,21 @@ impl AppState {
|
||||
&mut AppStateImpl::ProcessingEvents {
|
||||
ref mut event_handler,
|
||||
ref mut active_control_flow,
|
||||
} => {
|
||||
(
|
||||
ManuallyDrop::new(ptr::read(event_handler)),
|
||||
*active_control_flow,
|
||||
)
|
||||
},
|
||||
} => (
|
||||
ManuallyDrop::new(ptr::read(event_handler)),
|
||||
*active_control_flow,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let new = this.control_flow;
|
||||
match (old, new) {
|
||||
(ControlFlow::Poll, ControlFlow::Poll) => {
|
||||
ptr::write(
|
||||
&mut this.app_state,
|
||||
AppStateImpl::PollFinished {
|
||||
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
|
||||
},
|
||||
)
|
||||
},
|
||||
(ControlFlow::Poll, ControlFlow::Poll) => ptr::write(
|
||||
&mut this.app_state,
|
||||
AppStateImpl::PollFinished {
|
||||
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
|
||||
},
|
||||
),
|
||||
(ControlFlow::Wait, ControlFlow::Wait) => {
|
||||
let start = Instant::now();
|
||||
ptr::write(
|
||||
@@ -502,7 +490,7 @@ impl AppState {
|
||||
start,
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
|
||||
if old_instant == new_instant =>
|
||||
{
|
||||
@@ -525,7 +513,7 @@ impl AppState {
|
||||
},
|
||||
);
|
||||
this.waker.stop()
|
||||
},
|
||||
}
|
||||
(_, ControlFlow::WaitUntil(new_instant)) => {
|
||||
let start = Instant::now();
|
||||
ptr::write(
|
||||
@@ -536,7 +524,7 @@ impl AppState {
|
||||
},
|
||||
);
|
||||
this.waker.start_at(new_instant)
|
||||
},
|
||||
}
|
||||
(_, ControlFlow::Poll) => {
|
||||
ptr::write(
|
||||
&mut this.app_state,
|
||||
@@ -545,13 +533,13 @@ impl AppState {
|
||||
},
|
||||
);
|
||||
this.waker.start()
|
||||
},
|
||||
}
|
||||
(_, ControlFlow::Exit) => {
|
||||
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
||||
// it is not possible to quit an iOS app gracefully and programatically
|
||||
warn!("`ControlFlow::Exit` ignored on iOS");
|
||||
this.control_flow = old
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,9 +577,9 @@ impl EventLoopWaker {
|
||||
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
unsafe {
|
||||
// create a timer with a 1microsec interval (1ns does not work) to mimic polling.
|
||||
// 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
|
||||
// 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
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
std::f64::MAX,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ffi::c_void,
|
||||
fmt::{self, Debug, Formatter},
|
||||
fmt::{self, Debug},
|
||||
marker::PhantomData,
|
||||
mem, ptr,
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
@@ -142,8 +142,7 @@ pub struct EventLoopProxy<T> {
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for EventLoopProxy<T> {}
|
||||
unsafe impl<T> Sync for EventLoopProxy<T> {}
|
||||
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
||||
|
||||
impl<T> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> EventLoopProxy<T> {
|
||||
@@ -163,7 +162,7 @@ impl<T> Drop for EventLoopProxy<T> {
|
||||
impl<T> EventLoopProxy<T> {
|
||||
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
|
||||
unsafe {
|
||||
// just wakeup the eventloop
|
||||
// just wake up the eventloop
|
||||
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
|
||||
|
||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||
@@ -264,9 +263,8 @@ struct EventLoopHandler<F, T: 'static> {
|
||||
}
|
||||
|
||||
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("EventLoopHandler")
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EventLoopHandler")
|
||||
.field("event_loop", &self.event_loop)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#![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 crate::platform::ios::{Idiom, ValidOrientations};
|
||||
use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
|
||||
|
||||
pub type id = *mut Object;
|
||||
pub const nil: id = 0 as id;
|
||||
@@ -70,6 +70,24 @@ pub enum UITouchPhase {
|
||||
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)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UIEdgeInsets {
|
||||
@@ -161,18 +179,63 @@ impl UIInterfaceOrientationMask {
|
||||
match (valid_orientations, idiom) {
|
||||
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => {
|
||||
UIInterfaceOrientationMask::AllButUpsideDown
|
||||
},
|
||||
}
|
||||
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
|
||||
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
|
||||
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
|
||||
(ValidOrientations::Portrait, _) => {
|
||||
UIInterfaceOrientationMask::Portrait
|
||||
| UIInterfaceOrientationMask::PortraitUpsideDown
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIRectEdge(NSUInteger);
|
||||
|
||||
unsafe impl Encode for UIRectEdge {
|
||||
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 = "CoreFoundation", kind = "framework")]
|
||||
extern "C" {
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
//!
|
||||
//! This is how those event are represented in winit:
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Suspended(false)
|
||||
//! - applicationWillResignActive is Suspended(true)
|
||||
//! - applicationDidBecomeActive is Resumed
|
||||
//! - applicationWillResignActive is Suspended
|
||||
//! - applicationWillTerminate is LoopDestroyed
|
||||
//!
|
||||
//! Keep in mind that after LoopDestroyed event is received every attempt to draw with
|
||||
@@ -79,7 +79,7 @@ use std::fmt;
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,79 @@
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
collections::{BTreeSet, VecDeque},
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::VideoMode,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::ffi::{
|
||||
id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger,
|
||||
};
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
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");
|
||||
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 refresh_rate: NSInteger = msg_send![uiscreen, maximumFramesPerSecond];
|
||||
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 {
|
||||
uiscreen: id,
|
||||
}
|
||||
@@ -25,6 +86,7 @@ impl Drop for Inner {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle {
|
||||
inner: Inner,
|
||||
}
|
||||
@@ -106,9 +168,10 @@ impl MonitorHandle {
|
||||
impl Inner {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
unsafe {
|
||||
if self.uiscreen == main_uiscreen().uiscreen {
|
||||
let main = main_uiscreen();
|
||||
if self.uiscreen == main.uiscreen {
|
||||
Some("Primary".to_string())
|
||||
} else if self.uiscreen == mirrored_uiscreen().uiscreen {
|
||||
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
uiscreens()
|
||||
@@ -140,22 +203,18 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
let mut modes = BTreeSet::new();
|
||||
unsafe {
|
||||
let available_modes: id = msg_send![self.uiscreen, availableModes];
|
||||
let available_mode_count: NSUInteger = msg_send![available_modes, count];
|
||||
|
||||
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
|
||||
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
|
||||
|
||||
let mut modes = HashSet::with_capacity(available_mode_count);
|
||||
|
||||
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,
|
||||
});
|
||||
for i in 0..available_mode_count {
|
||||
let mode: id = msg_send![available_modes, objectAtIndex: i];
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
modes.into_iter()
|
||||
@@ -167,6 +226,15 @@ impl Inner {
|
||||
pub fn ui_screen(&self) -> id {
|
||||
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
|
||||
@@ -176,8 +244,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle {
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
unsafe fn mirrored_uiscreen() -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen];
|
||||
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,53 @@ use objc::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent},
|
||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||
platform::ios::MonitorHandleExtIOS,
|
||||
window::{WindowAttributes, WindowId as RootWindowId},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
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::{
|
||||
app_state::AppState,
|
||||
event_loop,
|
||||
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase},
|
||||
window::PlatformSpecificWindowBuilderAttributes,
|
||||
DeviceId,
|
||||
};
|
||||
macro_rules! add_property {
|
||||
(
|
||||
$decl:ident,
|
||||
$name:ident: $t:ty,
|
||||
$setter_name:ident: |$object:ident| $after_set:expr,
|
||||
$getter_name:ident,
|
||||
) => {
|
||||
{
|
||||
const VAR_NAME: &'static str = concat!("_", stringify!($name));
|
||||
$decl.add_ivar::<$t>(VAR_NAME);
|
||||
#[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
|
||||
}
|
||||
#[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_name 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
|
||||
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
@@ -59,8 +94,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
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,
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
};
|
||||
AppState::handle_nonuser_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
@@ -92,67 +127,56 @@ unsafe fn get_view_controller_class() -> &'static Class {
|
||||
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(
|
||||
sel!(setPrefersStatusBarHidden:),
|
||||
set_prefers_status_bar_hidden as extern "C" fn(&mut Object, Sel, BOOL),
|
||||
);
|
||||
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,
|
||||
);
|
||||
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: |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: |object| {
|
||||
unsafe {
|
||||
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}
|
||||
},
|
||||
preferredScreenEdgesDeferringSystemGestures,
|
||||
}
|
||||
CLASS = Some(decl.register());
|
||||
}
|
||||
CLASS.unwrap()
|
||||
@@ -195,6 +219,27 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
break;
|
||||
}
|
||||
let location: CGPoint = msg_send![touch, locationInView: nil];
|
||||
let touch_type: UITouchType = msg_send![touch, type];
|
||||
let trait_collection: id = msg_send![object, traitCollection];
|
||||
let touch_capability: UIForceTouchCapability =
|
||||
msg_send![trait_collection, forceTouchCapability];
|
||||
let force = 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
|
||||
};
|
||||
let touch_id = touch as u64;
|
||||
let phase: UITouchPhase = msg_send![touch, phase];
|
||||
let phase = match phase {
|
||||
@@ -212,6 +257,7 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
||||
id: touch_id,
|
||||
location: (location.x as f64, location.y as f64).into(),
|
||||
force,
|
||||
phase,
|
||||
}),
|
||||
});
|
||||
@@ -235,8 +281,8 @@ unsafe fn get_window_class() -> &'static Class {
|
||||
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,
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
};
|
||||
AppState::handle_nonuser_events(
|
||||
std::iter::once(Event::WindowEvent {
|
||||
@@ -308,7 +354,7 @@ pub unsafe fn create_view(
|
||||
|
||||
// requires main thread
|
||||
pub unsafe fn create_view_controller(
|
||||
window_attributes: &WindowAttributes,
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
view: id,
|
||||
) -> id {
|
||||
@@ -324,16 +370,24 @@ pub unsafe fn create_view_controller(
|
||||
!view_controller.is_null(),
|
||||
"Failed to initialize `UIViewController` instance"
|
||||
);
|
||||
let status_bar_hidden = if window_attributes.decorations {
|
||||
NO
|
||||
} else {
|
||||
let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
};
|
||||
let idiom = event_loop::get_idiom();
|
||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
||||
platform_attributes.valid_orientations,
|
||||
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![
|
||||
view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
@@ -342,6 +396,14 @@ pub unsafe fn create_view_controller(
|
||||
view_controller,
|
||||
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];
|
||||
view_controller
|
||||
}
|
||||
@@ -366,8 +428,16 @@ pub unsafe fn create_window(
|
||||
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
|
||||
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
|
||||
}
|
||||
if let &Some(ref monitor) = &window_attributes.fullscreen {
|
||||
let () = msg_send![window, setScreen:monitor.ui_screen()];
|
||||
match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let uiscreen = video_mode.monitor().ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
|
||||
}
|
||||
Some(Fullscreen::Borderless(ref monitor)) => {
|
||||
msg_send![window, setScreen:monitor.ui_screen()]
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
window
|
||||
@@ -382,11 +452,11 @@ pub fn create_delegate_class() {
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { AppState::handle_nonuser_event(Event::Suspended(false)) }
|
||||
unsafe { AppState::handle_nonuser_event(Event::Resumed) }
|
||||
}
|
||||
|
||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||
unsafe { AppState::handle_nonuser_event(Event::Suspended(true)) }
|
||||
unsafe { AppState::handle_nonuser_event(Event::Suspended) }
|
||||
}
|
||||
|
||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ops::{Deref, DerefMut},
|
||||
@@ -10,14 +11,17 @@ use crate::{
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
icon::Icon,
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform::ios::{MonitorHandleExtIOS, ValidOrientations},
|
||||
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
event_loop,
|
||||
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask},
|
||||
ffi::{
|
||||
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UIScreenOverscanCompensation,
|
||||
},
|
||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
||||
},
|
||||
window::{CursorIcon, WindowAttributes},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
pub struct Inner {
|
||||
@@ -63,8 +67,8 @@ impl Inner {
|
||||
unsafe {
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
Ok(LogicalPosition {
|
||||
x: safe_area.origin.x,
|
||||
y: safe_area.origin.y,
|
||||
x: safe_area.origin.x as _,
|
||||
y: safe_area.origin.y as _,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -73,8 +77,8 @@ impl Inner {
|
||||
unsafe {
|
||||
let screen_frame = self.screen_frame();
|
||||
Ok(LogicalPosition {
|
||||
x: screen_frame.origin.x,
|
||||
y: screen_frame.origin.y,
|
||||
x: screen_frame.origin.x as _,
|
||||
y: screen_frame.origin.y as _,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -98,8 +102,8 @@ impl Inner {
|
||||
unsafe {
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
LogicalSize {
|
||||
width: safe_area.size.width,
|
||||
height: safe_area.size.height,
|
||||
width: safe_area.size.width as _,
|
||||
height: safe_area.size.height as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,8 +112,8 @@ impl Inner {
|
||||
unsafe {
|
||||
let screen_frame = self.screen_frame();
|
||||
LogicalSize {
|
||||
width: screen_frame.size.width,
|
||||
height: screen_frame.size.height,
|
||||
width: screen_frame.size.width as _,
|
||||
height: screen_frame.size.height as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,26 +161,41 @@ impl Inner {
|
||||
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 {
|
||||
match monitor {
|
||||
Some(monitor) => {
|
||||
let uiscreen = monitor.ui_screen() as id;
|
||||
let current: id = msg_send![self.window, screen];
|
||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
let uiscreen = match monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
|
||||
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
|
||||
uiscreen
|
||||
}
|
||||
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
|
||||
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"),
|
||||
// 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 {
|
||||
let monitor = self.current_monitor();
|
||||
let uiscreen = monitor.inner.ui_screen();
|
||||
@@ -189,21 +208,15 @@ impl Inner {
|
||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||
{
|
||||
Some(monitor)
|
||||
Some(Fullscreen::Borderless(monitor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
unsafe {
|
||||
let status_bar_hidden = if decorations { NO } else { YES };
|
||||
let () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersStatusBarHidden: status_bar_hidden
|
||||
];
|
||||
}
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
warn!("`Window::set_decorations` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
||||
@@ -238,6 +251,16 @@ impl Inner {
|
||||
pub fn id(&self) -> WindowId {
|
||||
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 {
|
||||
@@ -293,22 +316,23 @@ impl Window {
|
||||
// TODO: transparency, visible
|
||||
|
||||
unsafe {
|
||||
let screen = window_attributes
|
||||
.fullscreen
|
||||
.as_ref()
|
||||
.map(|screen| screen.ui_screen() as _)
|
||||
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen());
|
||||
let screen = match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
video_mode.video_mode.monitor.ui_screen() as id
|
||||
}
|
||||
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 frame = match window_attributes.inner_size {
|
||||
Some(dim) => {
|
||||
CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
size: CGSize {
|
||||
width: dim.width,
|
||||
height: dim.height,
|
||||
},
|
||||
}
|
||||
Some(dim) => CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
size: CGSize {
|
||||
width: dim.width as _,
|
||||
height: dim.height as _,
|
||||
},
|
||||
},
|
||||
None => screen_bounds,
|
||||
};
|
||||
@@ -375,6 +399,36 @@ impl Inner {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let prefers_home_indicator_hidden = if hidden { NO } else { YES };
|
||||
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
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -496,6 +550,9 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub root_view_class: &'static Class,
|
||||
pub hidpi_factor: Option<f64>,
|
||||
pub valid_orientations: ValidOrientations,
|
||||
pub prefers_home_indicator_hidden: bool,
|
||||
pub prefers_status_bar_hidden: bool,
|
||||
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
@@ -504,6 +561,9 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
root_view_class: class!(UIView),
|
||||
hidpi_factor: None,
|
||||
valid_orientations: Default::default(),
|
||||
prefers_home_indicator_hidden: false,
|
||||
prefers_status_bar_hidden: false,
|
||||
preferred_screen_edges_deferring_system_gestures: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
#![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 raw_window_handle::RawWindowHandle;
|
||||
use smithay_client_toolkit::reexports::client::ConnectError;
|
||||
|
||||
pub use self::x11::XNotSupported;
|
||||
use self::x11::{ffi::XVisualInfo, XConnection, XError};
|
||||
use self::x11::{ffi::XVisualInfo, get_xtarget, XConnection, XError};
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
icon::Icon,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode},
|
||||
window::{CursorIcon, WindowAttributes},
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
mod dlopen;
|
||||
@@ -55,10 +56,10 @@ pub enum OsError {
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
OsError::XError(e) => formatter.pad(&e.description),
|
||||
OsError::XMisc(e) => formatter.pad(e),
|
||||
OsError::XError(e) => f.pad(&e.description),
|
||||
OsError::XMisc(e) => f.pad(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +93,7 @@ impl DeviceId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MonitorHandle {
|
||||
X(x11::MonitorHandle),
|
||||
Wayland(wayland::MonitorHandle),
|
||||
@@ -140,7 +141,7 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
|
||||
match self {
|
||||
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
||||
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
||||
@@ -148,6 +149,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 {
|
||||
#[inline]
|
||||
pub fn new<T>(
|
||||
@@ -158,10 +199,10 @@ impl Window {
|
||||
match *window_target {
|
||||
EventLoopWindowTarget::Wayland(ref window_target) => {
|
||||
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
|
||||
},
|
||||
}
|
||||
EventLoopWindowTarget::X(ref window_target) => {
|
||||
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,21 +351,15 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
match self {
|
||||
&Window::X(ref w) => w.fullscreen(),
|
||||
&Window::Wayland(ref w) => {
|
||||
w.fullscreen().map(|monitor_id| {
|
||||
RootMonitorHandle {
|
||||
inner: MonitorHandle::Wayland(monitor_id),
|
||||
}
|
||||
})
|
||||
},
|
||||
&Window::Wayland(ref w) => w.fullscreen(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
match self {
|
||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
||||
@@ -374,15 +409,11 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
||||
match self {
|
||||
&Window::X(ref window) => {
|
||||
RootMonitorHandle {
|
||||
inner: MonitorHandle::X(window.current_monitor()),
|
||||
}
|
||||
&Window::X(ref window) => RootMonitorHandle {
|
||||
inner: MonitorHandle::X(window.current_monitor()),
|
||||
},
|
||||
&Window::Wayland(ref window) => {
|
||||
RootMonitorHandle {
|
||||
inner: MonitorHandle::Wayland(window.current_monitor()),
|
||||
}
|
||||
&Window::Wayland(ref window) => RootMonitorHandle {
|
||||
inner: MonitorHandle::Wayland(window.current_monitor()),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -390,20 +421,16 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match self {
|
||||
&Window::X(ref window) => {
|
||||
window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect()
|
||||
},
|
||||
&Window::Wayland(ref window) => {
|
||||
window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect()
|
||||
},
|
||||
&Window::X(ref window) => window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
&Window::Wayland(ref window) => window
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +441,13 @@ impl Window {
|
||||
&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(
|
||||
@@ -422,14 +456,16 @@ unsafe extern "C" fn x_error_callback(
|
||||
) -> c_int {
|
||||
let xconn_lock = X11_BACKEND.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)(
|
||||
display,
|
||||
(*event).error_code as c_int,
|
||||
buf.as_mut_ptr(),
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
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 {
|
||||
description: description.into_owned(),
|
||||
@@ -451,12 +487,20 @@ pub enum EventLoop<T: 'static> {
|
||||
X(x11::EventLoop<T>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum EventLoopProxy<T: 'static> {
|
||||
X(x11::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> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
@@ -464,16 +508,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
"x11" => {
|
||||
// TODO: propagate
|
||||
return EventLoop::new_x11().expect("Failed to initialize X11 backend");
|
||||
},
|
||||
}
|
||||
"wayland" => {
|
||||
return EventLoop::new_wayland().expect("Failed to initialize Wayland backend");
|
||||
},
|
||||
_ => {
|
||||
panic!(
|
||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR,
|
||||
)
|
||||
},
|
||||
}
|
||||
_ => panic!(
|
||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
BACKEND_PREFERENCE_ENV_VAR,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,19 +553,17 @@ impl<T: 'static> EventLoop<T> {
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evlp) => {
|
||||
evlp.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect()
|
||||
},
|
||||
EventLoop::X(ref evlp) => {
|
||||
evlp.x_connection()
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect()
|
||||
},
|
||||
EventLoop::Wayland(ref evlp) => evlp
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::Wayland)
|
||||
.collect(),
|
||||
EventLoop::X(ref evlp) => get_xtarget(&evlp.target)
|
||||
.x_connection()
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(MonitorHandle::X)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,7 +571,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
match *self {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,14 +604,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> {
|
||||
match *self {
|
||||
EventLoop::Wayland(ref evl) => evl.window_target(),
|
||||
@@ -592,6 +626,16 @@ pub enum 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>(
|
||||
evt: Event<T>,
|
||||
target: &RootELW<T>,
|
||||
|
||||
@@ -7,15 +7,23 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::ModifiersState,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
monitor::VideoMode,
|
||||
platform_impl::platform::sticky_exit_callback,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::{
|
||||
sticky_exit_callback, MonitorHandle as PlatformMonitorHandle,
|
||||
VideoMode as PlatformVideoMode,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{window::WindowStore, WindowId};
|
||||
use super::{window::WindowStore, DeviceId, WindowId};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
output::OutputMgr,
|
||||
@@ -26,33 +34,37 @@ use smithay_client_toolkit::{
|
||||
Environment,
|
||||
};
|
||||
|
||||
pub struct WindowEventsSink {
|
||||
buffer: VecDeque<(crate::event::WindowEvent, crate::window::WindowId)>,
|
||||
pub struct WindowEventsSink<T> {
|
||||
buffer: VecDeque<crate::event::Event<T>>,
|
||||
}
|
||||
|
||||
impl WindowEventsSink {
|
||||
pub fn new() -> WindowEventsSink {
|
||||
impl<T> WindowEventsSink<T> {
|
||||
pub fn new() -> WindowEventsSink<T> {
|
||||
WindowEventsSink {
|
||||
buffer: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_event(&mut self, evt: crate::event::WindowEvent, wid: WindowId) {
|
||||
self.buffer.push_back((
|
||||
evt,
|
||||
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid)),
|
||||
));
|
||||
pub fn send_window_event(&mut self, evt: crate::event::WindowEvent, wid: WindowId) {
|
||||
self.buffer.push_back(crate::event::Event::WindowEvent {
|
||||
event: evt,
|
||||
window_id: crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid)),
|
||||
});
|
||||
}
|
||||
|
||||
fn empty_with<F, T>(&mut self, mut callback: F)
|
||||
pub fn send_device_event(&mut self, evt: crate::event::DeviceEvent, dev_id: DeviceId) {
|
||||
self.buffer.push_back(crate::event::Event::DeviceEvent {
|
||||
event: evt,
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(dev_id)),
|
||||
});
|
||||
}
|
||||
|
||||
fn empty_with<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(crate::event::Event<T>),
|
||||
{
|
||||
for (evt, wid) in self.buffer.drain(..) {
|
||||
callback(crate::event::Event::WindowEvent {
|
||||
event: evt,
|
||||
window_id: wid,
|
||||
})
|
||||
for evt in self.buffer.drain(..) {
|
||||
callback(evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +77,7 @@ pub struct EventLoop<T: 'static> {
|
||||
// the output manager
|
||||
pub outputs: OutputMgr,
|
||||
// our sink, shared with some handlers, buffering the events
|
||||
sink: Arc<Mutex<WindowEventsSink>>,
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
pending_user_events: Rc<RefCell<VecDeque<T>>>,
|
||||
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
|
||||
user_sender: ::calloop::channel::Sender<T>,
|
||||
@@ -78,7 +90,6 @@ pub struct EventLoop<T: 'static> {
|
||||
// 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.
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
user_sender: ::calloop::channel::Sender<T>,
|
||||
}
|
||||
@@ -99,6 +110,14 @@ pub struct EventLoopWindowTarget<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> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
||||
self.user_sender.send(event).map_err(|_| EventLoopClosed)
|
||||
@@ -122,13 +141,14 @@ impl<T: 'static> EventLoop<T> {
|
||||
.handle()
|
||||
.insert_source(kbd_channel, move |evt, &mut ()| {
|
||||
if let ::calloop::channel::Event::Msg((evt, wid)) = evt {
|
||||
kbd_sink.lock().unwrap().send_event(evt, wid);
|
||||
kbd_sink.lock().unwrap().send_window_event(evt, wid);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut seat_manager = SeatManager {
|
||||
sink: sink.clone(),
|
||||
relative_pointer_manager_proxy: None,
|
||||
store: store.clone(),
|
||||
seats: seats.clone(),
|
||||
kbd_sender,
|
||||
@@ -137,22 +157,29 @@ impl<T: 'static> EventLoop<T> {
|
||||
let env = Environment::from_display_with_cb(
|
||||
&display,
|
||||
&mut event_queue,
|
||||
move |event, registry| {
|
||||
match event {
|
||||
GlobalEvent::New {
|
||||
id,
|
||||
ref interface,
|
||||
version,
|
||||
} => {
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.add_seat(id, version, registry)
|
||||
}
|
||||
},
|
||||
GlobalEvent::Removed { id, ref interface } => {
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.remove_seat(id)
|
||||
}
|
||||
},
|
||||
move |event, registry| match event {
|
||||
GlobalEvent::New {
|
||||
id,
|
||||
ref interface,
|
||||
version,
|
||||
} => {
|
||||
if interface == "zwp_relative_pointer_manager_v1" {
|
||||
seat_manager.relative_pointer_manager_proxy = Some(
|
||||
registry
|
||||
.bind(version, id, move |pointer_manager| {
|
||||
pointer_manager.implement_closure(|_, _| (), ())
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.add_seat(id, version, registry)
|
||||
}
|
||||
}
|
||||
GlobalEvent::Removed { id, ref interface } => {
|
||||
if interface == "wl_seat" {
|
||||
seat_manager.remove_seat(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -299,7 +326,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
},
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
self.inner_loop.dispatch(None, &mut ()).unwrap();
|
||||
callback(
|
||||
@@ -310,7 +337,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
},
|
||||
}
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
let start = Instant::now();
|
||||
// compute the blocking duration
|
||||
@@ -344,7 +371,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,15 +390,17 @@ impl<T: 'static> EventLoop<T> {
|
||||
available_monitors(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &RootELW<T> {
|
||||
&self.window_target
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub fn display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Private EventLoop Internals
|
||||
*/
|
||||
@@ -390,7 +419,7 @@ impl<T> EventLoop<T> {
|
||||
let pruned = window_target.store.lock().unwrap().cleanup();
|
||||
*cleanup_needed = false;
|
||||
for wid in pruned {
|
||||
sink.send_event(crate::event::WindowEvent::Destroyed, wid);
|
||||
sink.send_window_event(crate::event::WindowEvent::Destroyed, wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +431,10 @@ impl<T> EventLoop<T> {
|
||||
frame.resize(w, h);
|
||||
frame.refresh();
|
||||
let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64);
|
||||
sink.send_event(crate::event::WindowEvent::Resized(logical_size), wid);
|
||||
sink.send_window_event(
|
||||
crate::event::WindowEvent::Resized(logical_size),
|
||||
wid,
|
||||
);
|
||||
*size = (w, h);
|
||||
} else if frame_refresh {
|
||||
frame.refresh();
|
||||
@@ -412,16 +444,16 @@ impl<T> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
if let Some(dpi) = new_dpi {
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64),
|
||||
wid,
|
||||
);
|
||||
}
|
||||
if refresh {
|
||||
sink.send_event(crate::event::WindowEvent::RedrawRequested, wid);
|
||||
sink.send_window_event(crate::event::WindowEvent::RedrawRequested, wid);
|
||||
}
|
||||
if closed {
|
||||
sink.send_event(crate::event::WindowEvent::CloseRequested, wid);
|
||||
sink.send_window_event(crate::event::WindowEvent::CloseRequested, wid);
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -432,14 +464,15 @@ impl<T> EventLoop<T> {
|
||||
* Wayland protocol implementations
|
||||
*/
|
||||
|
||||
struct SeatManager {
|
||||
sink: Arc<Mutex<WindowEventsSink>>,
|
||||
struct SeatManager<T: 'static> {
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
|
||||
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
||||
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
|
||||
}
|
||||
|
||||
impl SeatManager {
|
||||
impl<T: 'static> SeatManager<T> {
|
||||
fn add_seat(&mut self, id: u32, version: u32, registry: wl_registry::WlRegistry) {
|
||||
use std::cmp::min;
|
||||
|
||||
@@ -447,6 +480,8 @@ impl SeatManager {
|
||||
sink: self.sink.clone(),
|
||||
store: self.store.clone(),
|
||||
pointer: None,
|
||||
relative_pointer: None,
|
||||
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.as_ref().cloned(),
|
||||
keyboard: None,
|
||||
touch: None,
|
||||
kbd_sender: self.kbd_sender.clone(),
|
||||
@@ -472,17 +507,19 @@ impl SeatManager {
|
||||
}
|
||||
}
|
||||
|
||||
struct SeatData {
|
||||
sink: Arc<Mutex<WindowEventsSink>>,
|
||||
struct SeatData<T> {
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
|
||||
pointer: Option<wl_pointer::WlPointer>,
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>,
|
||||
keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
touch: Option<wl_touch::WlTouch>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl SeatData {
|
||||
impl<T: 'static> SeatData<T> {
|
||||
fn receive(&mut self, evt: wl_seat::Event, seat: wl_seat::WlSeat) {
|
||||
match evt {
|
||||
wl_seat::Event::Name { .. } => (),
|
||||
@@ -494,7 +531,19 @@ impl SeatData {
|
||||
self.sink.clone(),
|
||||
self.store.clone(),
|
||||
self.modifiers_tracker.clone(),
|
||||
))
|
||||
));
|
||||
|
||||
self.relative_pointer =
|
||||
self.relative_pointer_manager_proxy
|
||||
.as_ref()
|
||||
.and_then(|manager| {
|
||||
super::pointer::implement_relative_pointer(
|
||||
self.sink.clone(),
|
||||
self.pointer.as_ref().unwrap(),
|
||||
manager,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
// destroy pointer if applicable
|
||||
if !capabilities.contains(wl_seat::Capability::Pointer) {
|
||||
@@ -536,13 +585,13 @@ impl SeatData {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SeatData {
|
||||
impl<T> Drop for SeatData<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(pointer) = self.pointer.take() {
|
||||
if pointer.as_ref().version() >= 3 {
|
||||
@@ -566,17 +615,67 @@ impl Drop for SeatData {
|
||||
* 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(crate) proxy: wl_output::WlOutput,
|
||||
pub(crate) mgr: OutputMgr,
|
||||
}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> MonitorHandle {
|
||||
MonitorHandle {
|
||||
proxy: self.proxy.clone(),
|
||||
mgr: self.mgr.clone(),
|
||||
}
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.native_identifier() == other.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,17 +742,20 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
#[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
|
||||
.with_info(&self.proxy, |_, info| info.modes.clone())
|
||||
.unwrap_or(vec![])
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
VideoMode {
|
||||
.map(move |x| RootVideoMode {
|
||||
video_mode: PlatformVideoMode::Wayland(VideoMode {
|
||||
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
|
||||
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
|
||||
bit_depth: 32,
|
||||
}
|
||||
monitor: monitor.clone(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -674,11 +776,9 @@ pub fn primary_monitor(outputs: &OutputMgr) -> MonitorHandle {
|
||||
pub fn available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorHandle> {
|
||||
outputs.with_all(|list| {
|
||||
list.iter()
|
||||
.map(|&(_, ref proxy, _)| {
|
||||
MonitorHandle {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
}
|
||||
.map(|&(_, ref proxy, _)| MonitorHandle {
|
||||
proxy: proxy.clone(),
|
||||
mgr: outputs.clone(),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
||||
@@ -31,12 +31,12 @@ pub fn init_keyboard(
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
|
||||
*target.lock().unwrap() = Some(wid);
|
||||
},
|
||||
}
|
||||
KbEvent::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
|
||||
*target.lock().unwrap() = None;
|
||||
},
|
||||
}
|
||||
KbEvent::Key {
|
||||
rawkey,
|
||||
keysym,
|
||||
@@ -79,8 +79,8 @@ pub fn init_keyboard(
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ },
|
||||
}
|
||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||
KbEvent::Modifiers {
|
||||
modifiers: event_modifiers,
|
||||
} => *modifiers_tracker.lock().unwrap() = event_modifiers.into(),
|
||||
@@ -127,59 +127,56 @@ pub fn init_keyboard(
|
||||
// In this case, we don't have the keymap information (it is
|
||||
// supposed to be serialized by the compositor using libxkbcommon)
|
||||
|
||||
// { variables to be captured by the closure
|
||||
let mut target = None;
|
||||
let my_sink = sink;
|
||||
// }
|
||||
seat.get_keyboard(|keyboard| {
|
||||
// { variables to be captured by the closure
|
||||
let mut target = None;
|
||||
let my_sink = sink;
|
||||
// }
|
||||
|
||||
keyboard.implement_closure(
|
||||
move |evt, _| {
|
||||
match evt {
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
|
||||
target = Some(wid);
|
||||
},
|
||||
wl_keyboard::Event::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
|
||||
target = None;
|
||||
},
|
||||
wl_keyboard::Event::Key { key, state, .. } => {
|
||||
if let Some(wid) = target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
my_sink
|
||||
.send((
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
),
|
||||
),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
move |evt, _| match evt {
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(true), wid)).unwrap();
|
||||
target = Some(wid);
|
||||
}
|
||||
wl_keyboard::Event::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send((WindowEvent::Focused(false), wid)).unwrap();
|
||||
target = None;
|
||||
}
|
||||
wl_keyboard::Event::Key { key, state, .. } => {
|
||||
if let Some(wid) = target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
my_sink
|
||||
.send((
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
},
|
||||
wid,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
pub use self::{
|
||||
event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink,
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
|
||||
WindowEventsSink,
|
||||
},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::event::{
|
||||
ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
|
||||
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
use super::{event_loop::WindowEventsSink, window::WindowStore, DeviceId};
|
||||
@@ -11,18 +12,23 @@ use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_seat,
|
||||
};
|
||||
|
||||
pub fn implement_pointer(
|
||||
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
};
|
||||
|
||||
pub fn implement_pointer<T: 'static>(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: Arc<Mutex<WindowEventsSink>>,
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> WlPointer {
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
let mut axis_state = TouchPhase::Ended;
|
||||
|
||||
seat.get_pointer(|pointer| {
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
let mut axis_state = TouchPhase::Ended;
|
||||
|
||||
pointer.implement_closure(
|
||||
move |evt, pointer| {
|
||||
let mut sink = sink.lock().unwrap();
|
||||
@@ -37,7 +43,7 @@ pub fn implement_pointer(
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
mouse_focus = Some(wid);
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -45,7 +51,7 @@ pub fn implement_pointer(
|
||||
},
|
||||
wid,
|
||||
);
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -56,12 +62,12 @@ pub fn implement_pointer(
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
PtrEvent::Leave { surface, .. } => {
|
||||
mouse_focus = None;
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -70,14 +76,14 @@ pub fn implement_pointer(
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
PtrEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -88,7 +94,7 @@ pub fn implement_pointer(
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
PtrEvent::Button { button, state, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
let state = match state {
|
||||
@@ -103,7 +109,7 @@ pub fn implement_pointer(
|
||||
// TODO figure out the translation ?
|
||||
_ => return,
|
||||
};
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -115,7 +121,7 @@ pub fn implement_pointer(
|
||||
wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
PtrEvent::Axis { axis, value, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if pointer.as_ref().version() < 5 {
|
||||
@@ -127,7 +133,7 @@ pub fn implement_pointer(
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -155,13 +161,13 @@ pub fn implement_pointer(
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
PtrEvent::Frame => {
|
||||
let axis_buffer = axis_buffer.take();
|
||||
let axis_discrete_buffer = axis_discrete_buffer.take();
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -173,7 +179,7 @@ pub fn implement_pointer(
|
||||
wid,
|
||||
);
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
@@ -188,11 +194,11 @@ pub fn implement_pointer(
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
PtrEvent::AxisSource { .. } => (),
|
||||
PtrEvent::AxisStop { .. } => {
|
||||
axis_state = TouchPhase::Ended;
|
||||
},
|
||||
}
|
||||
PtrEvent::AxisDiscrete { axis, discrete } => {
|
||||
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0));
|
||||
match axis {
|
||||
@@ -206,7 +212,7 @@ pub fn implement_pointer(
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
@@ -215,3 +221,23 @@ pub fn implement_pointer(
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn implement_relative_pointer<T: 'static>(
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
pointer: &WlPointer,
|
||||
manager: &ZwpRelativePointerManagerV1,
|
||||
) -> Result<ZwpRelativePointerV1, ()> {
|
||||
manager.get_relative_pointer(pointer, |rel_pointer| {
|
||||
rel_pointer.implement_closure(
|
||||
move |evt, _rel_pointer| {
|
||||
let mut sink = sink.lock().unwrap();
|
||||
match evt {
|
||||
Event::RelativeMotion { dx, dy, .. } => sink
|
||||
.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ struct TouchPoint {
|
||||
id: i32,
|
||||
}
|
||||
|
||||
pub(crate) fn implement_touch(
|
||||
pub(crate) fn implement_touch<T: 'static>(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: Arc<Mutex<WindowEventsSink>>,
|
||||
sink: Arc<Mutex<WindowEventsSink<T>>>,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
) -> WlTouch {
|
||||
let mut pending_ids = Vec::new();
|
||||
@@ -32,13 +32,14 @@ pub(crate) fn implement_touch(
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Started,
|
||||
location: (x, y).into(),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
wid,
|
||||
@@ -49,57 +50,60 @@ pub(crate) fn implement_touch(
|
||||
id,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let idx = pending_ids.iter().position(|p| p.id == id);
|
||||
if let Some(idx) = idx {
|
||||
let pt = pending_ids.remove(idx);
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Ended,
|
||||
location: pt.location.into(),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let pt = pending_ids.iter_mut().find(|p| p.id == id);
|
||||
if let Some(pt) = pt {
|
||||
pt.location = (x, y);
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Moved,
|
||||
location: (x, y).into(),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => {
|
||||
for pt in pending_ids.drain(..) {
|
||||
sink.send_event(
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: pt.location.into(),
|
||||
force: None, // TODO
|
||||
id: pt.id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use raw_window_handle::unix::WaylandHandle;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{Seek, SeekFrom, Write},
|
||||
sync::{Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
@@ -9,29 +9,27 @@ use crate::{
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform_impl::{
|
||||
platform::wayland::event_loop::{available_monitors, primary_monitor},
|
||||
MonitorHandle as PlatformMonitorHandle,
|
||||
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
||||
},
|
||||
window::{CursorIcon, WindowAttributes},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
output::OutputMgr,
|
||||
reexports::client::{
|
||||
protocol::{wl_seat, wl_shm, wl_subsurface, wl_surface},
|
||||
Display, NewProxy,
|
||||
protocol::{wl_seat, wl_surface},
|
||||
Display,
|
||||
},
|
||||
surface::{get_dpi_factor, get_outputs},
|
||||
window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow},
|
||||
};
|
||||
|
||||
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
||||
use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
|
||||
|
||||
pub struct Window {
|
||||
_bg_surface: wl_surface::WlSurface,
|
||||
user_surface: wl_surface::WlSurface,
|
||||
_user_subsurface: wl_subsurface::WlSubsurface,
|
||||
surface: wl_surface::WlSurface,
|
||||
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
|
||||
outputs: OutputMgr, // Access to info for all monitors
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
@@ -54,78 +52,49 @@ impl Window {
|
||||
let fullscreen = Arc::new(Mutex::new(false));
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
let bg_surface = evlp
|
||||
.env
|
||||
.compositor
|
||||
.create_surface(NewProxy::implement_dummy)
|
||||
.unwrap();
|
||||
let user_surface = evlp.env.create_surface(move |dpi, surface| {
|
||||
let surface = evlp.env.create_surface(move |dpi, surface| {
|
||||
window_store.lock().unwrap().dpi_change(&surface, dpi);
|
||||
surface.set_buffer_scale(dpi);
|
||||
});
|
||||
let user_subsurface = evlp
|
||||
.env
|
||||
.subcompositor
|
||||
.get_subsurface(&user_surface, &bg_surface, NewProxy::implement_dummy)
|
||||
.unwrap();
|
||||
user_subsurface.set_desync();
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
let my_surface = user_surface.clone();
|
||||
let my_bg_surface = bg_surface.clone();
|
||||
|
||||
// prepare a 1px buffer to display on the root window
|
||||
let mut pool = smithay_client_toolkit::utils::MemPool::new(&evlp.env.shm, || {}).unwrap();
|
||||
pool.resize(4).unwrap();
|
||||
pool.seek(SeekFrom::Start(0)).unwrap();
|
||||
pool.write(&[0, 0, 0, 0]).unwrap();
|
||||
pool.flush().unwrap();
|
||||
let buffer = pool.buffer(0, 1, 1, 4, wl_shm::Format::Argb8888);
|
||||
|
||||
let my_surface = surface.clone();
|
||||
let mut frame = SWindow::<ConceptFrame>::init_from_env(
|
||||
&evlp.env,
|
||||
bg_surface.clone(),
|
||||
surface.clone(),
|
||||
(width, height),
|
||||
move |event| {
|
||||
match event {
|
||||
WEvent::Configure { new_size, states } => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
let is_fullscreen = states.contains(&WState::Fullscreen);
|
||||
move |event| match event {
|
||||
WEvent::Configure { new_size, states } => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
let is_fullscreen = states.contains(&WState::Fullscreen);
|
||||
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.newsize = new_size;
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
if !window.configured {
|
||||
// this is our first configure event, display ourselves !
|
||||
window.configured = true;
|
||||
my_bg_surface.attach(Some(&buffer), 0, 0);
|
||||
my_bg_surface.commit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.newsize = new_size;
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
},
|
||||
WEvent::Refresh => {
|
||||
let store = window_store.lock().unwrap();
|
||||
for window in &store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Refresh => {
|
||||
let store = window_store.lock().unwrap();
|
||||
for window in &store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
},
|
||||
WEvent::Close => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Close => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -140,13 +109,19 @@ impl Window {
|
||||
}
|
||||
|
||||
// Check for fullscreen requirements
|
||||
if let Some(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
}) = attributes.fullscreen
|
||||
{
|
||||
frame.set_fullscreen(Some(&monitor_id.proxy));
|
||||
} else if attributes.maximized {
|
||||
frame.set_maximized();
|
||||
match attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
panic!("Wayland doesn't support exclusive fullscreen")
|
||||
}
|
||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
|
||||
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||
None => {
|
||||
if attributes.maximized {
|
||||
frame.set_maximized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame.set_resizable(attributes.resizable);
|
||||
@@ -173,19 +148,17 @@ impl Window {
|
||||
need_refresh: need_refresh.clone(),
|
||||
fullscreen: fullscreen.clone(),
|
||||
need_frame_refresh: need_frame_refresh.clone(),
|
||||
surface: user_surface.clone(),
|
||||
surface: surface.clone(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
frame: Arc::downgrade(&frame),
|
||||
current_dpi: 1,
|
||||
new_dpi: None,
|
||||
configured: false,
|
||||
});
|
||||
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
|
||||
|
||||
Ok(Window {
|
||||
display: evlp.display.clone(),
|
||||
_bg_surface: bg_surface,
|
||||
user_surface,
|
||||
_user_subsurface: user_subsurface,
|
||||
surface,
|
||||
frame,
|
||||
outputs: evlp.env.outputs.clone(),
|
||||
size,
|
||||
@@ -198,7 +171,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
make_wid(&self.user_surface)
|
||||
make_wid(&self.surface)
|
||||
}
|
||||
|
||||
pub fn set_title(&self, title: &str) {
|
||||
@@ -270,7 +243,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn hidpi_factor(&self) -> i32 {
|
||||
get_dpi_factor(&self.user_surface)
|
||||
get_dpi_factor(&self.surface)
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
@@ -286,25 +259,31 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Option<MonitorHandle> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
if *(self.fullscreen.lock().unwrap()) {
|
||||
Some(self.current_monitor())
|
||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
if let Some(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
}) = monitor
|
||||
{
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_fullscreen(Some(&monitor_id.proxy));
|
||||
} else {
|
||||
self.frame.lock().unwrap().unset_fullscreen();
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
match fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
panic!("Wayland doesn't support exclusive fullscreen")
|
||||
}
|
||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||
})) => {
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_fullscreen(Some(&monitor_id.proxy));
|
||||
}
|
||||
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||
None => self.frame.lock().unwrap().unset_fullscreen(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,11 +316,11 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn surface(&self) -> &wl_surface::WlSurface {
|
||||
&self.user_surface
|
||||
&self.surface
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> MonitorHandle {
|
||||
let output = get_outputs(&self.user_surface).last().unwrap().clone();
|
||||
let output = get_outputs(&self.surface).last().unwrap().clone();
|
||||
MonitorHandle {
|
||||
proxy: output,
|
||||
mgr: self.outputs.clone(),
|
||||
@@ -355,6 +334,14 @@ impl Window {
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
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 {
|
||||
@@ -380,7 +367,6 @@ struct InternalWindow {
|
||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
||||
current_dpi: i32,
|
||||
new_dpi: Option<i32>,
|
||||
configured: bool,
|
||||
}
|
||||
|
||||
pub struct WindowStore {
|
||||
|
||||
@@ -118,7 +118,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
wt.xconn
|
||||
.check_errors()
|
||||
.expect("Failed to call XRefreshKeyboardMapping");
|
||||
},
|
||||
}
|
||||
|
||||
ffi::ClientMessage => {
|
||||
let client_msg: &ffi::XClientMessageEvent = xev.as_ref();
|
||||
@@ -131,6 +131,16 @@ impl<T: 'static> EventProcessor<T> {
|
||||
window_id,
|
||||
event: WindowEvent::CloseRequested,
|
||||
});
|
||||
} else if client_msg.data.get_long(0) as ffi::Atom == wt.net_wm_ping {
|
||||
let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut();
|
||||
response_msg.window = wt.root;
|
||||
wt.xconn
|
||||
.send_event(
|
||||
wt.root,
|
||||
Some(ffi::SubstructureNotifyMask | ffi::SubstructureRedirectMask),
|
||||
*response_msg,
|
||||
)
|
||||
.queue();
|
||||
} else if client_msg.message_type == self.dnd.atoms.enter {
|
||||
let source_window = client_msg.data.get_long(0) as c_ulong;
|
||||
let flags = client_msg.data.get_long(1);
|
||||
@@ -236,7 +246,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: WindowEvent::HoveredFileCancelled,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::SelectionNotify => {
|
||||
let xsel: &ffi::XSelectionEvent = xev.as_ref();
|
||||
@@ -263,7 +273,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
self.dnd.result = result;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::ConfigureNotify => {
|
||||
#[derive(Debug, Default)]
|
||||
@@ -429,7 +439,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
callback(Event::WindowEvent { window_id, event });
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::ReparentNotify => {
|
||||
let xev: &ffi::XReparentEvent = xev.as_ref();
|
||||
@@ -444,7 +454,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
self.with_window(xev.window, |window| {
|
||||
window.invalidate_cached_frame_extents();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
ffi::DestroyNotify => {
|
||||
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
|
||||
@@ -467,7 +477,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
window_id,
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
ffi::Expose => {
|
||||
let xev: &ffi::XExposeEvent = xev.as_ref();
|
||||
@@ -479,7 +489,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
window_id,
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
ffi::KeyPress | ffi::KeyRelease => {
|
||||
use crate::event::ElementState::{Pressed, Released};
|
||||
@@ -554,7 +564,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::GenericEvent => {
|
||||
let guard = if let Some(e) = GenericEventCookie::from_event(&wt.xconn, *xev) {
|
||||
@@ -596,39 +606,33 @@ impl<T: 'static> EventProcessor<T> {
|
||||
Released
|
||||
};
|
||||
match xev.detail as u32 {
|
||||
ffi::Button1 => {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Left,
|
||||
modifiers,
|
||||
},
|
||||
})
|
||||
},
|
||||
ffi::Button2 => {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Middle,
|
||||
modifiers,
|
||||
},
|
||||
})
|
||||
},
|
||||
ffi::Button3 => {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Right,
|
||||
modifiers,
|
||||
},
|
||||
})
|
||||
},
|
||||
ffi::Button1 => callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Left,
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
ffi::Button2 => callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Middle,
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
ffi::Button3 => callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Right,
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
|
||||
// Suppress emulated scroll wheel clicks, since we handle the real motion events for those.
|
||||
// In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in
|
||||
@@ -651,21 +655,19 @@ impl<T: 'static> EventProcessor<T> {
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
x => {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Other(x as u8),
|
||||
modifiers,
|
||||
},
|
||||
})
|
||||
},
|
||||
x => callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: MouseInput {
|
||||
device_id,
|
||||
state,
|
||||
button: Other(x as u8),
|
||||
modifiers,
|
||||
},
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
ffi::XI_Motion => {
|
||||
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
||||
let device_id = mkdid(xev.deviceid);
|
||||
@@ -734,11 +736,11 @@ impl<T: 'static> EventProcessor<T> {
|
||||
delta: match info.orientation {
|
||||
ScrollOrientation::Horizontal => {
|
||||
LineDelta(delta as f32, 0.0)
|
||||
},
|
||||
}
|
||||
// X11 vertical scroll coordinates are opposite to winit's
|
||||
ScrollOrientation::Vertical => {
|
||||
LineDelta(0.0, -delta as f32)
|
||||
},
|
||||
}
|
||||
},
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers,
|
||||
@@ -761,7 +763,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
for event in events {
|
||||
callback(event);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::XI_Enter => {
|
||||
let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) };
|
||||
@@ -821,7 +823,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
ffi::XI_Leave => {
|
||||
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
@@ -836,7 +838,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
ffi::XI_FocusIn => {
|
||||
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
@@ -878,7 +880,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
modifiers: ModifiersState::from(xev.mods),
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
ffi::XI_FocusOut => {
|
||||
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
|
||||
if !self.window_exists(xev.event) {
|
||||
@@ -892,7 +894,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
window_id: mkwid(xev.event),
|
||||
event: Focused(false),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
|
||||
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
||||
@@ -916,11 +918,12 @@ impl<T: 'static> EventProcessor<T> {
|
||||
device_id: mkdid(xev.deviceid),
|
||||
phase,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: xev.detail as u64,
|
||||
}),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => {
|
||||
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
||||
@@ -937,7 +940,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::XI_RawMotion => {
|
||||
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
||||
@@ -962,7 +965,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
1 => mouse_delta.1 = x,
|
||||
2 => scroll_delta.0 = x as f32,
|
||||
3 => scroll_delta.1 = x as f32,
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
callback(Event::DeviceEvent {
|
||||
device_id: did,
|
||||
@@ -988,7 +991,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => {
|
||||
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
||||
@@ -1033,7 +1036,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
modifiers: ModifiersState::default(),
|
||||
}),
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
ffi::XI_HierarchyChanged => {
|
||||
let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) };
|
||||
@@ -1056,11 +1059,11 @@ impl<T: 'static> EventProcessor<T> {
|
||||
devices.remove(&DeviceId(info.deviceid));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
if event_type == self.randr_event_offset {
|
||||
// In the future, it would be quite easy to emit monitor hotplug events.
|
||||
@@ -1101,13 +1104,13 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
match self.ime_receiver.try_recv() {
|
||||
Ok((window_id, x, y)) => {
|
||||
wt.ime.borrow_mut().send_xim_spot(window_id, x, y);
|
||||
},
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ mod window;
|
||||
mod xdisplay;
|
||||
|
||||
pub use self::{
|
||||
monitor::MonitorHandle,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::UnownedWindow,
|
||||
xdisplay::{XConnection, XError, XNotSupported},
|
||||
};
|
||||
@@ -20,7 +20,7 @@ use std::{
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
ffi::CStr,
|
||||
mem,
|
||||
mem::{self, MaybeUninit},
|
||||
ops::Deref,
|
||||
os::raw::*,
|
||||
rc::Rc,
|
||||
@@ -46,6 +46,7 @@ use crate::{
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
xconn: Arc<XConnection>,
|
||||
wm_delete_window: ffi::Atom,
|
||||
net_wm_ping: ffi::Atom,
|
||||
ime_sender: ImeSender,
|
||||
root: ffi::Window,
|
||||
ime: RefCell<Ime>,
|
||||
@@ -59,22 +60,32 @@ pub struct EventLoop<T: 'static> {
|
||||
_x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>,
|
||||
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
|
||||
pending_user_events: Rc<RefCell<VecDeque<T>>>,
|
||||
event_processor: Rc<RefCell<EventProcessor<T>>>,
|
||||
user_sender: ::calloop::channel::Sender<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> {
|
||||
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> {
|
||||
pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
|
||||
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
||||
|
||||
let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") };
|
||||
|
||||
let net_wm_ping = unsafe { xconn.get_atom_unchecked(b"_NET_WM_PING\0") };
|
||||
|
||||
let dnd = Dnd::new(Arc::clone(&xconn))
|
||||
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
||||
|
||||
@@ -97,22 +108,21 @@ impl<T: 'static> EventLoop<T> {
|
||||
.expect("Failed to query XRandR extension");
|
||||
|
||||
let xi2ext = unsafe {
|
||||
let mut result = XExtension {
|
||||
opcode: mem::uninitialized(),
|
||||
first_event_id: mem::uninitialized(),
|
||||
first_error_id: mem::uninitialized(),
|
||||
};
|
||||
let mut ext = XExtension::default();
|
||||
|
||||
let res = (xconn.xlib.XQueryExtension)(
|
||||
xconn.display,
|
||||
b"XInputExtension\0".as_ptr() as *const c_char,
|
||||
&mut result.opcode as *mut c_int,
|
||||
&mut result.first_event_id as *mut c_int,
|
||||
&mut result.first_error_id as *mut c_int,
|
||||
&mut ext.opcode,
|
||||
&mut ext.first_event_id,
|
||||
&mut ext.first_error_id,
|
||||
);
|
||||
|
||||
if res == ffi::False {
|
||||
panic!("X server missing XInput extension");
|
||||
}
|
||||
result
|
||||
|
||||
ext
|
||||
};
|
||||
|
||||
unsafe {
|
||||
@@ -142,6 +152,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
ime_sender,
|
||||
xconn,
|
||||
wm_delete_window,
|
||||
net_wm_ping,
|
||||
pending_redraws: Default::default(),
|
||||
}),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
@@ -168,7 +179,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Handle X11 events
|
||||
let pending_events: Rc<RefCell<VecDeque<_>>> = Default::default();
|
||||
|
||||
let mut processor = EventProcessor {
|
||||
let processor = EventProcessor {
|
||||
target: target.clone(),
|
||||
dnd,
|
||||
devices: Default::default(),
|
||||
@@ -186,6 +197,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
processor.init_device(ffi::XIAllDevices);
|
||||
|
||||
let processor = Rc::new(RefCell::new(processor));
|
||||
let event_processor = processor.clone();
|
||||
|
||||
// Setup the X11 event source
|
||||
let mut x11_events =
|
||||
::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd);
|
||||
@@ -194,16 +208,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
.handle()
|
||||
.insert_source(x11_events, {
|
||||
let pending_events = pending_events.clone();
|
||||
let mut callback = move |event| {
|
||||
pending_events.borrow_mut().push_back(event);
|
||||
};
|
||||
move |evt, &mut ()| {
|
||||
if evt.readiness.is_readable() {
|
||||
// process all pending events
|
||||
let mut xev = unsafe { mem::uninitialized() };
|
||||
while unsafe { processor.poll_one_event(&mut xev) } {
|
||||
processor.process_event(&mut xev, &mut callback);
|
||||
}
|
||||
let mut processor = processor.borrow_mut();
|
||||
let mut pending_events = pending_events.borrow_mut();
|
||||
drain_events(&mut processor, &mut pending_events);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -216,18 +225,13 @@ impl<T: 'static> EventLoop<T> {
|
||||
_user_source,
|
||||
user_sender,
|
||||
pending_user_events,
|
||||
event_processor,
|
||||
target,
|
||||
};
|
||||
|
||||
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> {
|
||||
EventLoopProxy {
|
||||
user_sender: self.user_sender.clone(),
|
||||
@@ -245,6 +249,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
let mut control_flow = ControlFlow::default();
|
||||
let wt = get_xtarget(&self.target);
|
||||
|
||||
callback(
|
||||
crate::event::Event::NewEvents(crate::event::StartCause::Init),
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
);
|
||||
|
||||
loop {
|
||||
// Empty the event buffer
|
||||
{
|
||||
@@ -268,8 +278,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
// Empty the redraw requests
|
||||
{
|
||||
let mut guard = wt.pending_redraws.lock().unwrap();
|
||||
for wid in guard.drain() {
|
||||
// Release the lock to prevent deadlock
|
||||
let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect();
|
||||
|
||||
for wid in windows {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(super::WindowId::X(wid)),
|
||||
@@ -291,11 +303,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
);
|
||||
}
|
||||
|
||||
// flush the X11 connection
|
||||
unsafe {
|
||||
(wt.xconn.xlib.XFlush)(wt.xconn.display);
|
||||
}
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break,
|
||||
ControlFlow::Poll => {
|
||||
@@ -308,7 +315,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
);
|
||||
},
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
self.inner_loop.dispatch(None, &mut ()).unwrap();
|
||||
callback(
|
||||
@@ -319,7 +326,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
&self.target,
|
||||
&mut control_flow,
|
||||
);
|
||||
},
|
||||
}
|
||||
ControlFlow::WaitUntil(deadline) => {
|
||||
let start = ::std::time::Instant::now();
|
||||
// compute the blocking duration
|
||||
@@ -353,8 +360,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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(
|
||||
@@ -371,13 +382,43 @@ impl<T: 'static> EventLoop<T> {
|
||||
self.run_return(callback);
|
||||
::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> {
|
||||
if let super::EventLoopWindowTarget::X(ref target) = rt.p {
|
||||
target
|
||||
} else {
|
||||
unreachable!();
|
||||
fn drain_events<T: 'static>(
|
||||
processor: &mut EventProcessor<T>,
|
||||
pending_events: &mut VecDeque<Event<T>>,
|
||||
) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,19 +437,19 @@ struct DeviceInfo<'a> {
|
||||
impl<'a> DeviceInfo<'a> {
|
||||
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
|
||||
unsafe {
|
||||
let mut count = mem::uninitialized();
|
||||
let mut count = 0;
|
||||
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
|
||||
xconn.check_errors().ok().and_then(|_| {
|
||||
if info.is_null() || count == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(DeviceInfo {
|
||||
xconn,
|
||||
info,
|
||||
count: count as usize,
|
||||
})
|
||||
}
|
||||
})
|
||||
xconn.check_errors().ok()?;
|
||||
|
||||
if info.is_null() || count == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(DeviceInfo {
|
||||
xconn,
|
||||
info,
|
||||
count: count as usize,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,7 +554,7 @@ impl<'a> Drop for GenericEventCookie<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
struct XExtension {
|
||||
opcode: c_int,
|
||||
first_event_id: c_int,
|
||||
@@ -588,8 +629,8 @@ impl Device {
|
||||
position: 0.0,
|
||||
},
|
||||
));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -619,8 +660,8 @@ impl Device {
|
||||
{
|
||||
axis.position = info.value;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,47 +4,66 @@ use parking_lot::Mutex;
|
||||
|
||||
use super::{
|
||||
ffi::{
|
||||
RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window,
|
||||
XRRScreenResources,
|
||||
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
|
||||
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
|
||||
},
|
||||
util, XConnection, XError,
|
||||
};
|
||||
use crate::{
|
||||
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.
|
||||
const FORCE_RANDR_COMPAT: bool = false;
|
||||
// Also used for testing. This should always be committed as false.
|
||||
// Used for testing. This should always be committed as false.
|
||||
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
||||
|
||||
lazy_static! {
|
||||
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = 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>> {
|
||||
// We update this lazily.
|
||||
(*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)]
|
||||
pub struct MonitorHandle {
|
||||
/// The actual id
|
||||
id: u32,
|
||||
pub(crate) id: RRCrtc,
|
||||
/// The name of the monitor
|
||||
pub(crate) name: String,
|
||||
/// The size of the monitor
|
||||
@@ -61,16 +80,43 @@ pub struct MonitorHandle {
|
||||
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 {
|
||||
fn from_repr(
|
||||
fn new(
|
||||
xconn: &XConnection,
|
||||
resources: *mut XRRScreenResources,
|
||||
id: u32,
|
||||
repr: util::MonitorRepr,
|
||||
id: RRCrtc,
|
||||
crtc: *mut XRRCrtcInfo,
|
||||
primary: bool,
|
||||
) -> Option<Self> {
|
||||
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? };
|
||||
let (dimensions, position) = unsafe { (repr.size(), repr.position()) };
|
||||
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
|
||||
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);
|
||||
Some(MonitorHandle {
|
||||
id,
|
||||
@@ -107,8 +153,14 @@ impl MonitorHandle {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
self.video_modes.clone().into_iter()
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
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),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +191,12 @@ impl XConnection {
|
||||
|
||||
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
|
||||
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 version_is_at_least(1, 3) {
|
||||
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||
} else {
|
||||
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
||||
@@ -155,48 +211,19 @@ impl XConnection {
|
||||
let mut available;
|
||||
let mut has_primary = false;
|
||||
|
||||
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
|
||||
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
|
||||
// videowalls.
|
||||
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
|
||||
let mut monitor_count = 0;
|
||||
let monitors =
|
||||
(xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
|
||||
assert!(monitor_count >= 0);
|
||||
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;
|
||||
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 is_primary = *(*crtc).outputs.offset(0) == primary;
|
||||
has_primary |= is_primary;
|
||||
MonitorHandle::from_repr(
|
||||
self,
|
||||
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);
|
||||
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
|
||||
.map(|monitor_id| available.push(monitor_id));
|
||||
}
|
||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||
}
|
||||
|
||||
// If no monitors were detected as being primary, we just pick one ourselves!
|
||||
@@ -236,19 +263,15 @@ impl XConnection {
|
||||
}
|
||||
|
||||
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
|
||||
{
|
||||
let mut version_lock = XRANDR_VERSION.lock();
|
||||
if version_lock.is_none() {
|
||||
let mut major = 0;
|
||||
let mut minor = 0;
|
||||
let has_extension =
|
||||
unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) };
|
||||
if has_extension != True {
|
||||
panic!("[winit] XRandR extension not available.");
|
||||
}
|
||||
*version_lock = Some((major, minor));
|
||||
}
|
||||
}
|
||||
let has_xrandr = unsafe {
|
||||
let mut major = 0;
|
||||
let mut minor = 0;
|
||||
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
|
||||
};
|
||||
assert!(
|
||||
has_xrandr == True,
|
||||
"[winit] XRandR extension not available."
|
||||
);
|
||||
|
||||
let mut event_offset = 0;
|
||||
let mut error_offset = 0;
|
||||
|
||||
@@ -30,13 +30,17 @@ impl XConnection {
|
||||
event_mask: Option<c_long>,
|
||||
data: ClientMsgPayload,
|
||||
) -> Flusher<'_> {
|
||||
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
|
||||
event.type_ = ffi::ClientMessage;
|
||||
event.display = self.display;
|
||||
event.window = window;
|
||||
event.message_type = message_type;
|
||||
event.format = c_long::FORMAT as c_int;
|
||||
event.data = unsafe { mem::transmute(data) };
|
||||
let event = ffi::XClientMessageEvent {
|
||||
type_: ffi::ClientMessage,
|
||||
display: self.display,
|
||||
window,
|
||||
message_type,
|
||||
format: c_long::FORMAT as c_int,
|
||||
data: unsafe { mem::transmute(data) },
|
||||
// These fields are ignored by `XSendEvent`
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
self.send_event(target_window, event_mask, event)
|
||||
}
|
||||
|
||||
@@ -54,12 +58,17 @@ impl XConnection {
|
||||
let format = T::FORMAT;
|
||||
let size_of_t = mem::size_of::<T>();
|
||||
debug_assert_eq!(size_of_t, format.get_actual_size());
|
||||
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() };
|
||||
event.type_ = ffi::ClientMessage;
|
||||
event.display = self.display;
|
||||
event.window = window;
|
||||
event.message_type = message_type;
|
||||
event.format = format as c_int;
|
||||
let mut event = ffi::XClientMessageEvent {
|
||||
type_: ffi::ClientMessage,
|
||||
display: self.display,
|
||||
window,
|
||||
message_type,
|
||||
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;
|
||||
assert!(t_per_payload > 0);
|
||||
|
||||
@@ -41,14 +41,14 @@ impl AaRect {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TranslatedCoords {
|
||||
pub x_rel_root: c_int,
|
||||
pub y_rel_root: c_int,
|
||||
pub child: ffi::Window,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Geometry {
|
||||
pub root: ffi::Window,
|
||||
// If you want positions relative to the root window, use translate_coords.
|
||||
@@ -183,7 +183,8 @@ impl XConnection {
|
||||
window: ffi::Window,
|
||||
root: ffi::Window,
|
||||
) -> Result<TranslatedCoords, XError> {
|
||||
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
|
||||
let mut coords = TranslatedCoords::default();
|
||||
|
||||
unsafe {
|
||||
(self.xlib.XTranslateCoordinates)(
|
||||
self.display,
|
||||
@@ -191,18 +192,20 @@ impl XConnection {
|
||||
root,
|
||||
0,
|
||||
0,
|
||||
&mut translated_coords.x_rel_root,
|
||||
&mut translated_coords.y_rel_root,
|
||||
&mut translated_coords.child,
|
||||
&mut coords.x_rel_root,
|
||||
&mut coords.y_rel_root,
|
||||
&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
|
||||
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 {
|
||||
(self.xlib.XGetGeometry)(
|
||||
self.display,
|
||||
@@ -216,8 +219,9 @@ impl XConnection {
|
||||
&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> {
|
||||
@@ -264,10 +268,10 @@ impl XConnection {
|
||||
|
||||
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
|
||||
let parent = unsafe {
|
||||
let mut root: ffi::Window = mem::uninitialized();
|
||||
let mut parent: ffi::Window = mem::uninitialized();
|
||||
let mut root = 0;
|
||||
let mut parent = 0;
|
||||
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?
|
||||
let _status = (self.xlib.XQueryTree)(
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub const MWM_HINTS_DECORATIONS: c_ulong = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StateOperation {
|
||||
Remove = 0, // _NET_WM_STATE_REMOVE
|
||||
@@ -93,6 +92,92 @@ impl WindowType {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MotifHints {
|
||||
hints: MwmHints,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct MwmHints {
|
||||
flags: c_ulong,
|
||||
functions: c_ulong,
|
||||
decorations: c_ulong,
|
||||
input_mode: c_long,
|
||||
status: c_ulong,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod mwm {
|
||||
use libc::c_ulong;
|
||||
|
||||
// Motif WM hints are obsolete, but still widely supported.
|
||||
// https://stackoverflow.com/a/1909708
|
||||
pub const MWM_HINTS_FUNCTIONS: c_ulong = 1 << 0;
|
||||
pub const MWM_HINTS_DECORATIONS: c_ulong = 1 << 1;
|
||||
|
||||
pub const MWM_FUNC_ALL: c_ulong = 1 << 0;
|
||||
pub const MWM_FUNC_RESIZE: c_ulong = 1 << 1;
|
||||
pub const MWM_FUNC_MOVE: c_ulong = 1 << 2;
|
||||
pub const MWM_FUNC_MINIMIZE: c_ulong = 1 << 3;
|
||||
pub const MWM_FUNC_MAXIMIZE: c_ulong = 1 << 4;
|
||||
pub const MWM_FUNC_CLOSE: c_ulong = 1 << 5;
|
||||
}
|
||||
|
||||
impl MotifHints {
|
||||
pub fn new() -> MotifHints {
|
||||
MotifHints {
|
||||
hints: MwmHints {
|
||||
flags: 0,
|
||||
functions: 0,
|
||||
decorations: 0,
|
||||
input_mode: 0,
|
||||
status: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_decorations(&mut self, decorations: bool) {
|
||||
self.hints.flags |= mwm::MWM_HINTS_DECORATIONS;
|
||||
self.hints.decorations = decorations as c_ulong;
|
||||
}
|
||||
|
||||
pub fn set_maximizable(&mut self, maximizable: bool) {
|
||||
if maximizable {
|
||||
self.add_func(mwm::MWM_FUNC_MAXIMIZE);
|
||||
} else {
|
||||
self.remove_func(mwm::MWM_FUNC_MAXIMIZE);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_func(&mut self, func: c_ulong) {
|
||||
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 {
|
||||
if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
|
||||
self.hints.functions &= !func;
|
||||
} else {
|
||||
self.hints.functions |= func;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_func(&mut self, func: c_ulong) {
|
||||
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 {
|
||||
self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS;
|
||||
self.hints.functions = mwm::MWM_FUNC_ALL;
|
||||
}
|
||||
|
||||
if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
|
||||
self.hints.functions |= func;
|
||||
} else {
|
||||
self.hints.functions &= !func;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MwmHints {
|
||||
fn as_slice(&self) -> &[c_ulong] {
|
||||
unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NormalHints<'a> {
|
||||
size_hints: XSmartPointer<'a, ffi::XSizeHints>,
|
||||
}
|
||||
@@ -232,13 +317,13 @@ impl XConnection {
|
||||
|
||||
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
|
||||
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 {
|
||||
(self.xlib.XGetWMNormalHints)(
|
||||
self.display,
|
||||
window,
|
||||
size_hints.ptr,
|
||||
&mut supplied_by_user,
|
||||
supplied_by_user.as_mut_ptr(),
|
||||
);
|
||||
}
|
||||
self.check_errors().map(|_| NormalHints { size_hints })
|
||||
@@ -254,4 +339,32 @@ impl XConnection {
|
||||
}
|
||||
Flusher::new(self)
|
||||
}
|
||||
|
||||
pub fn get_motif_hints(&self, window: ffi::Window) -> MotifHints {
|
||||
let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") };
|
||||
|
||||
let mut hints = MotifHints::new();
|
||||
|
||||
if let Ok(props) = self.get_property::<c_ulong>(window, motif_hints, motif_hints) {
|
||||
hints.hints.flags = props.get(0).cloned().unwrap_or(0);
|
||||
hints.hints.functions = props.get(1).cloned().unwrap_or(0);
|
||||
hints.hints.decorations = props.get(2).cloned().unwrap_or(0);
|
||||
hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long;
|
||||
hints.hints.status = props.get(4).cloned().unwrap_or(0);
|
||||
}
|
||||
|
||||
hints
|
||||
}
|
||||
|
||||
pub fn set_motif_hints(&self, window: ffi::Window, hints: &MotifHints) -> Flusher<'_> {
|
||||
let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") };
|
||||
|
||||
self.change_property(
|
||||
window,
|
||||
motif_hints,
|
||||
motif_hints,
|
||||
PropMode::Replace,
|
||||
hints.hints.as_slice(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::str;
|
||||
use std::{slice, str};
|
||||
|
||||
use super::*;
|
||||
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> {
|
||||
xconn: &'a XConnection,
|
||||
root: ffi::Window,
|
||||
child: ffi::Window,
|
||||
pub root: ffi::Window,
|
||||
pub child: ffi::Window,
|
||||
pub root_x: c_double,
|
||||
pub root_y: c_double,
|
||||
win_x: c_double,
|
||||
win_y: c_double,
|
||||
pub win_x: c_double,
|
||||
pub win_y: c_double,
|
||||
buttons: ffi::XIButtonState,
|
||||
modifiers: ffi::XIModifierState,
|
||||
group: ffi::XIGroupState,
|
||||
relative_to_window: bool,
|
||||
pub group: ffi::XIGroupState,
|
||||
pub relative_to_window: bool,
|
||||
}
|
||||
|
||||
impl<'a> PointerState<'a> {
|
||||
@@ -93,29 +94,46 @@ impl XConnection {
|
||||
device_id: c_int,
|
||||
) -> Result<PointerState<'_>, XError> {
|
||||
unsafe {
|
||||
let mut pointer_state: PointerState<'_> = mem::uninitialized();
|
||||
pointer_state.xconn = self;
|
||||
pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)(
|
||||
let mut root = 0;
|
||||
let mut child = 0;
|
||||
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,
|
||||
device_id,
|
||||
window,
|
||||
&mut pointer_state.root,
|
||||
&mut pointer_state.child,
|
||||
&mut pointer_state.root_x,
|
||||
&mut pointer_state.root_y,
|
||||
&mut pointer_state.win_x,
|
||||
&mut pointer_state.win_y,
|
||||
&mut pointer_state.buttons,
|
||||
&mut pointer_state.modifiers,
|
||||
&mut pointer_state.group,
|
||||
&mut root,
|
||||
&mut child,
|
||||
&mut root_x,
|
||||
&mut root_y,
|
||||
&mut win_x,
|
||||
&mut win_y,
|
||||
&mut buttons,
|
||||
&mut modifiers,
|
||||
&mut group,
|
||||
) == ffi::True;
|
||||
if let Err(err) = self.check_errors() {
|
||||
// Running the destrutor would be bad news for us...
|
||||
mem::forget(pointer_state);
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(pointer_state)
|
||||
}
|
||||
|
||||
self.check_errors()?;
|
||||
|
||||
Ok(PointerState {
|
||||
xconn: self,
|
||||
root,
|
||||
child,
|
||||
root_x,
|
||||
root_y,
|
||||
win_x,
|
||||
win_y,
|
||||
buttons,
|
||||
modifiers,
|
||||
group,
|
||||
relative_to_window,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +141,8 @@ impl XConnection {
|
||||
&self,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: &mut [u8],
|
||||
buffer: *mut u8,
|
||||
size: usize,
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
@@ -131,8 +150,8 @@ impl XConnection {
|
||||
(self.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
buffer.len() as c_int,
|
||||
buffer as *mut c_char,
|
||||
size as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
)
|
||||
@@ -141,21 +160,28 @@ impl XConnection {
|
||||
}
|
||||
|
||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
||||
let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() };
|
||||
let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
|
||||
// The buffer overflowed, so we'll make a new one on the heap.
|
||||
if status == ffi::XBufferOverflow {
|
||||
let mut buffer = Vec::with_capacity(count as usize);
|
||||
unsafe { buffer.set_len(count as usize) };
|
||||
let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
// If the buffer overflows, we'll make a new one on the heap.
|
||||
let mut vec;
|
||||
|
||||
let (_, status, count) =
|
||||
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
|
||||
|
||||
let bytes = if status == ffi::XBufferOverflow {
|
||||
vec = Vec::with_capacity(count as usize);
|
||||
let (_, _, new_count) =
|
||||
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
|
||||
debug_assert_eq!(count, new_count);
|
||||
str::from_utf8(&buffer[..count as usize])
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
|
||||
unsafe { vec.set_len(count as usize) };
|
||||
&vec[..count as usize]
|
||||
} else {
|
||||
str::from_utf8(&buffer[..count as usize])
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
}
|
||||
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
|
||||
};
|
||||
|
||||
str::from_utf8(bytes).unwrap_or("").to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,12 @@ pub use self::{
|
||||
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};
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::{env, slice, str::FromStr};
|
||||
|
||||
use super::*;
|
||||
use crate::{dpi::validate_hidpi_factor, monitor::VideoMode};
|
||||
use super::{
|
||||
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
|
||||
*,
|
||||
};
|
||||
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
|
||||
|
||||
pub fn calc_dpi_factor(
|
||||
(width_px, height_px): (u32, u32),
|
||||
@@ -22,7 +25,7 @@ pub fn calc_dpi_factor(
|
||||
}
|
||||
|
||||
// See http://xpra.org/trac/ticket/728 for more information.
|
||||
if width_mm == 0 || width_mm == 0 {
|
||||
if width_mm == 0 || height_mm == 0 {
|
||||
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
|
||||
return 1.0;
|
||||
}
|
||||
@@ -34,47 +37,6 @@ pub fn calc_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 {
|
||||
// Retrieve DPI from Xft.dpi property
|
||||
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
||||
@@ -96,11 +58,11 @@ impl XConnection {
|
||||
}
|
||||
pub unsafe fn get_output_info(
|
||||
&self,
|
||||
resources: *mut ffi::XRRScreenResources,
|
||||
repr: &MonitorRepr,
|
||||
resources: *mut XRRScreenResources,
|
||||
crtc: *mut XRRCrtcInfo,
|
||||
) -> Option<(String, f64, Vec<VideoMode>)> {
|
||||
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() {
|
||||
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
||||
// it's possible for it to return null.
|
||||
@@ -132,6 +94,10 @@ impl XConnection {
|
||||
size: (x.width, x.height),
|
||||
refresh_rate: (refresh_rate as f32 / 1000.0).round() 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.
|
||||
} else {
|
||||
calc_dpi_factor(
|
||||
repr.size(),
|
||||
((*crtc).width as u32, (*crtc).height as u32),
|
||||
(
|
||||
(*output_info).mm_width as u64,
|
||||
(*output_info).mm_height as u64,
|
||||
@@ -155,4 +121,61 @@ impl XConnection {
|
||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
||||
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 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 {
|
||||
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.display,
|
||||
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, MaybeUninit},
|
||||
os::raw::*,
|
||||
path::Path,
|
||||
ptr, slice,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use libc;
|
||||
use parking_lot::Mutex;
|
||||
@@ -6,12 +17,13 @@ use parking_lot::Mutex;
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::{
|
||||
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
|
||||
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};
|
||||
@@ -36,9 +48,11 @@ pub struct SharedState {
|
||||
pub guessed_dpi: Option<f64>,
|
||||
pub last_monitor: Option<X11MonitorHandle>,
|
||||
pub dpi_adjusted: Option<(f64, f64)>,
|
||||
pub fullscreen: Option<RootMonitorHandle>,
|
||||
// Used to restore position after exiting fullscreen.
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
// Used to restore position after exiting fullscreen
|
||||
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 min_inner_size: Option<LogicalSize>,
|
||||
pub max_inner_size: Option<LogicalSize>,
|
||||
@@ -330,8 +344,9 @@ impl UnownedWindow {
|
||||
(xconn.xlib.XSetWMProtocols)(
|
||||
xconn.display,
|
||||
window.xwindow,
|
||||
&event_loop.wm_delete_window as *const ffi::Atom as *mut ffi::Atom,
|
||||
1,
|
||||
&[event_loop.wm_delete_window, event_loop.net_wm_ping] as *const ffi::Atom
|
||||
as *mut ffi::Atom,
|
||||
2,
|
||||
);
|
||||
} //.queue();
|
||||
|
||||
@@ -384,7 +399,7 @@ impl UnownedWindow {
|
||||
ImeContextCreationError::XError(err) => OsError::XError(err),
|
||||
ImeContextCreationError::Null => {
|
||||
OsError::XMisc("IME Context creation failed")
|
||||
},
|
||||
}
|
||||
};
|
||||
return Err(os_error!(e));
|
||||
}
|
||||
@@ -397,6 +412,7 @@ impl UnownedWindow {
|
||||
if window_attrs.fullscreen.is_some() {
|
||||
window
|
||||
.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
||||
.unwrap()
|
||||
.queue();
|
||||
}
|
||||
if window_attrs.always_on_top {
|
||||
@@ -409,11 +425,11 @@ impl UnownedWindow {
|
||||
unsafe {
|
||||
// XSetInputFocus generates an error if the window is not visible, so we wait
|
||||
// until we receive VisibilityNotify.
|
||||
let mut event = mem::uninitialized();
|
||||
let mut event = MaybeUninit::uninit();
|
||||
(xconn.xlib.XIfEvent)(
|
||||
// This will flush the request buffer IF it blocks.
|
||||
xconn.display,
|
||||
&mut event as *mut ffi::XEvent,
|
||||
event.as_mut_ptr(),
|
||||
Some(visibility_predicate),
|
||||
window.xwindow as _,
|
||||
);
|
||||
@@ -448,19 +464,22 @@ impl UnownedWindow {
|
||||
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") };
|
||||
unsafe {
|
||||
let (hostname, hostname_length) = {
|
||||
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
|
||||
// the limit defined by OpenBSD.
|
||||
const MAXHOSTNAMELEN: usize = 256;
|
||||
let mut hostname: [c_char; MAXHOSTNAMELEN] = mem::uninitialized();
|
||||
let status = libc::gethostname(hostname.as_mut_ptr(), hostname.len());
|
||||
if status != 0 {
|
||||
return None;
|
||||
}
|
||||
hostname[MAXHOSTNAMELEN - 1] = '\0' as c_char; // a little extra safety
|
||||
let hostname_length = libc::strlen(hostname.as_ptr());
|
||||
(hostname, hostname_length as usize)
|
||||
};
|
||||
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
|
||||
// the limit defined by OpenBSD.
|
||||
const MAXHOSTNAMELEN: usize = 256;
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buffer: [MaybeUninit<c_char>; MAXHOSTNAMELEN] =
|
||||
MaybeUninit::uninit().assume_init();
|
||||
let status = libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len());
|
||||
if status != 0 {
|
||||
return None;
|
||||
}
|
||||
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
|
||||
.change_property(
|
||||
self.xwindow,
|
||||
@@ -550,41 +569,122 @@ impl UnownedWindow {
|
||||
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0))
|
||||
}
|
||||
|
||||
fn set_fullscreen_inner(&self, monitor: Option<RootMonitorHandle>) -> util::Flusher<'_> {
|
||||
match monitor {
|
||||
fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
|
||||
let mut shared_state_lock = self.shared_state.lock();
|
||||
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 => {
|
||||
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();
|
||||
}
|
||||
flusher
|
||||
},
|
||||
Some(RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::X(monitor),
|
||||
}) => {
|
||||
Some(flusher)
|
||||
}
|
||||
Some(fullscreen) => {
|
||||
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!(),
|
||||
};
|
||||
|
||||
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();
|
||||
self.shared_state.lock().restore_position = Some(window_position);
|
||||
let monitor_origin: (i32, i32) = monitor.position().into();
|
||||
self.set_position_inner(monitor_origin.0, monitor_origin.1)
|
||||
.queue();
|
||||
self.set_fullscreen_hint(true)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
Some(self.set_fullscreen_hint(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
self.shared_state.lock().fullscreen.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
self.shared_state.lock().fullscreen = monitor.clone();
|
||||
self.set_fullscreen_inner(monitor)
|
||||
.flush()
|
||||
.expect("Failed to change window fullscreen state");
|
||||
self.invalidate_cached_frame_extents();
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
if let Some(flusher) = self.set_fullscreen_inner(fullscreen) {
|
||||
flusher
|
||||
.flush()
|
||||
.expect("Failed to change window fullscreen state");
|
||||
self.invalidate_cached_frame_extents();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rect(&self) -> util::AaRect {
|
||||
@@ -666,20 +766,11 @@ impl UnownedWindow {
|
||||
}
|
||||
|
||||
fn set_decorations_inner(&self, decorations: bool) -> util::Flusher<'_> {
|
||||
let wm_hints = unsafe { self.xconn.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") };
|
||||
self.xconn.change_property(
|
||||
self.xwindow,
|
||||
wm_hints,
|
||||
wm_hints,
|
||||
util::PropMode::Replace,
|
||||
&[
|
||||
util::MWM_HINTS_DECORATIONS, // flags
|
||||
0, // functions
|
||||
decorations as c_ulong, // decorations
|
||||
0, // input mode
|
||||
0, // status
|
||||
],
|
||||
)
|
||||
let mut hints = self.xconn.get_motif_hints(self.xwindow);
|
||||
|
||||
hints.set_decorations(decorations);
|
||||
|
||||
self.xconn.set_motif_hints(self.xwindow, &hints)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -690,6 +781,14 @@ impl UnownedWindow {
|
||||
self.invalidate_cached_frame_extents();
|
||||
}
|
||||
|
||||
fn set_maximizable_inner(&self, maximizable: bool) -> util::Flusher<'_> {
|
||||
let mut hints = self.xconn.get_motif_hints(self.xwindow);
|
||||
|
||||
hints.set_maximizable(maximizable);
|
||||
|
||||
self.xconn.set_motif_hints(self.xwindow, &hints)
|
||||
}
|
||||
|
||||
fn set_always_on_top_inner(&self, always_on_top: bool) -> util::Flusher<'_> {
|
||||
let above_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_ABOVE\0") };
|
||||
self.set_netwm(always_on_top.into(), (above_atom as c_long, 0, 0, 0))
|
||||
@@ -984,6 +1083,8 @@ impl UnownedWindow {
|
||||
(window_size.clone(), window_size)
|
||||
};
|
||||
|
||||
self.set_maximizable_inner(resizable).queue();
|
||||
|
||||
let dpi_factor = self.hidpi_factor();
|
||||
let min_inner_size = logical_min
|
||||
.map(|logical_size| logical_size.to_physical(dpi_factor))
|
||||
@@ -1132,13 +1233,13 @@ impl UnownedWindow {
|
||||
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 mut dummy_color = MaybeUninit::uninit();
|
||||
let cursor = (self.xconn.xlib.XCreatePixmapCursor)(
|
||||
self.xconn.display,
|
||||
pixmap,
|
||||
pixmap,
|
||||
&dummy_color as *const _ as *mut _,
|
||||
&dummy_color as *const _ as *mut _,
|
||||
dummy_color.as_mut_ptr(),
|
||||
dummy_color.as_mut_ptr(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
@@ -1190,11 +1291,11 @@ impl UnownedWindow {
|
||||
ffi::GrabSuccess => Ok(()),
|
||||
ffi::AlreadyGrabbed => {
|
||||
Err("Cursor could not be grabbed: already grabbed by another client")
|
||||
},
|
||||
}
|
||||
ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"),
|
||||
ffi::GrabNotViewable => {
|
||||
Err("Cursor could not be grabbed: grab location not viewable")
|
||||
},
|
||||
}
|
||||
ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -1275,4 +1376,13 @@ impl UnownedWindow {
|
||||
.unwrap()
|
||||
.insert(WindowId(self.xwindow));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> X11Handle {
|
||||
X11Handle {
|
||||
window: self.xwindow,
|
||||
display: self.xconn.display as _,
|
||||
..X11Handle::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use objc::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
event::{DeviceEvent, Event},
|
||||
event::{DeviceEvent, ElementState, Event},
|
||||
platform_impl::platform::{app_state::AppState, util, DEVICE_ID},
|
||||
};
|
||||
|
||||
@@ -100,7 +100,33 @@ unsafe fn maybe_dispatch_device_event(event: id) {
|
||||
}
|
||||
|
||||
AppState::queue_events(events);
|
||||
},
|
||||
}
|
||||
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
|
||||
events.push_back(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: DeviceEvent::Button {
|
||||
button: event.buttonNumber() as u32,
|
||||
state: ElementState::Pressed,
|
||||
},
|
||||
});
|
||||
|
||||
AppState::queue_events(events);
|
||||
}
|
||||
appkit::NSLeftMouseUp | appkit::NSRightMouseUp | appkit::NSOtherMouseUp => {
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
|
||||
events.push_back(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: DeviceEvent::Button {
|
||||
button: event.buttonNumber() as u32,
|
||||
state: ElementState::Released,
|
||||
},
|
||||
});
|
||||
|
||||
AppState::queue_events(events);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL {
|
||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `didBecomeActive`");
|
||||
/*unsafe {
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(false))
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Resumed)
|
||||
}*/
|
||||
trace!("Completed `didBecomeActive`");
|
||||
}
|
||||
@@ -62,7 +62,7 @@ extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `willResignActive`");
|
||||
/*unsafe {
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(true))
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended)
|
||||
}*/
|
||||
trace!("Completed `willResignActive`");
|
||||
}
|
||||
|
||||
@@ -196,6 +196,7 @@ impl AppState {
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_nonuser_event(Event::LoopDestroyed);
|
||||
HANDLER.set_in_callback(false);
|
||||
HANDLER.callback.lock().unwrap().take();
|
||||
}
|
||||
|
||||
pub fn launched() {
|
||||
@@ -213,11 +214,9 @@ impl AppState {
|
||||
let start = HANDLER.get_start_time().unwrap();
|
||||
let cause = match HANDLER.get_control_flow_and_update_prev() {
|
||||
ControlFlow::Poll => StartCause::Poll,
|
||||
ControlFlow::Wait => {
|
||||
StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
}
|
||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
ControlFlow::WaitUntil(requested_resume) => {
|
||||
if Instant::now() >= requested_resume {
|
||||
@@ -231,7 +230,7 @@ impl AppState {
|
||||
requested_resume: Some(requested_resume),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
ControlFlow::Exit => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
|
||||
};
|
||||
HANDLER.set_in_callback(true);
|
||||
|
||||
@@ -108,19 +108,23 @@ impl<T> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Proxy<T> {
|
||||
sender: mpsc::Sender<T>,
|
||||
source: CFRunLoopSourceRef,
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for Proxy<T> {}
|
||||
unsafe impl<T> Sync for Proxy<T> {}
|
||||
unsafe impl<T: Send> Send for Proxy<T> {}
|
||||
|
||||
impl<T> Clone for Proxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Proxy::new(self.sender.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Proxy<T> {
|
||||
fn new(sender: mpsc::Sender<T>) -> Self {
|
||||
unsafe {
|
||||
// just wakeup the eventloop
|
||||
// just wake up the eventloop
|
||||
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
|
||||
|
||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
||||
|
||||
@@ -6,6 +6,13 @@ use cocoa::{
|
||||
base::id,
|
||||
foundation::{NSInteger, NSUInteger},
|
||||
};
|
||||
use core_foundation::{
|
||||
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
|
||||
};
|
||||
use core_graphics::{
|
||||
base::CGError,
|
||||
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
||||
};
|
||||
use objc;
|
||||
|
||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
||||
@@ -108,3 +115,95 @@ pub enum NSWindowLevel {
|
||||
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey 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::{
|
||||
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
||||
monitor::MonitorHandle,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
||||
};
|
||||
use crate::{
|
||||
|
||||
@@ -1,25 +1,119 @@
|
||||
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::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
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::{
|
||||
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
|
||||
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::VideoMode,
|
||||
platform_impl::platform::util::IdRef,
|
||||
};
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct VideoMode {
|
||||
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);
|
||||
|
||||
// `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> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
@@ -101,7 +195,7 @@ impl MonitorHandle {
|
||||
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 mut display_link = std::ptr::null_mut();
|
||||
assert_eq!(
|
||||
@@ -117,11 +211,27 @@ impl MonitorHandle {
|
||||
time.timeScale as i64 / time.timeValue
|
||||
};
|
||||
|
||||
CGDisplayMode::all_display_modes(self.0, std::ptr::null())
|
||||
.expect("failed to obtain list of display modes")
|
||||
.into_iter()
|
||||
.map(move |mode| {
|
||||
let cg_refresh_rate = mode.refresh_rate().round() as i64;
|
||||
let monitor = self.clone();
|
||||
|
||||
unsafe {
|
||||
let modes = {
|
||||
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
|
||||
// isn't a CRT
|
||||
@@ -131,34 +241,55 @@ impl MonitorHandle {
|
||||
cv_refresh_rate
|
||||
};
|
||||
|
||||
VideoMode {
|
||||
size: (mode.width() as u32, mode.height() as u32),
|
||||
let pixel_encoding =
|
||||
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,
|
||||
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> {
|
||||
unsafe {
|
||||
let native_id = self.native_identifier();
|
||||
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
|
||||
let screens = NSScreen::screens(nil);
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
let mut matching_screen: Option<id> = None;
|
||||
for i in 0..count {
|
||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let value: id = msg_send![device_description, objectForKey:*key];
|
||||
if value != nil {
|
||||
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue];
|
||||
if screen_number as u32 == native_id {
|
||||
matching_screen = Some(screen);
|
||||
break;
|
||||
let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
|
||||
let other_uuid =
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
|
||||
if uuid == other_uuid {
|
||||
return Some(screen);
|
||||
}
|
||||
}
|
||||
}
|
||||
matching_screen
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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")]
|
||||
extern "C" {
|
||||
@@ -13,7 +13,7 @@ extern "C" {
|
||||
pub fn CFRunLoopObserverCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
activities: CFOptionFlags,
|
||||
repeats: Boolean,
|
||||
repeats: ffi::Boolean,
|
||||
order: CFIndex,
|
||||
callout: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
@@ -51,11 +51,6 @@ extern "C" {
|
||||
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 type CFAllocatorRef = *mut CFAllocator;
|
||||
pub enum CFRunLoop {}
|
||||
@@ -102,7 +97,7 @@ pub struct CFRunLoopSourceContext {
|
||||
pub retain: extern "C" fn(*const c_void) -> *const c_void,
|
||||
pub release: extern "C" fn(*const c_void),
|
||||
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 schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||
@@ -121,7 +116,7 @@ extern "C" fn control_flow_begin_handler(
|
||||
//trace!("Triggered `CFRunLoopAfterWaiting`");
|
||||
AppState::wakeup();
|
||||
//trace!("Completed `CFRunLoopAfterWaiting`");
|
||||
},
|
||||
}
|
||||
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -140,7 +135,7 @@ extern "C" fn control_flow_end_handler(
|
||||
//trace!("Triggered `CFRunLoopBeforeWaiting`");
|
||||
AppState::cleared();
|
||||
//trace!("Completed `CFRunLoopBeforeWaiting`");
|
||||
},
|
||||
}
|
||||
kCFRunLoopExit => (), //unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -162,8 +157,8 @@ impl RunLoop {
|
||||
let observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
flags,
|
||||
TRUE, // Indicates we want this to run repeatedly
|
||||
priority, // The lower the value, the sooner this will run
|
||||
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||
priority, // The lower the value, the sooner this will run
|
||||
handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
@@ -204,9 +199,9 @@ impl Default for EventLoopWaker {
|
||||
fn default() -> EventLoopWaker {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
unsafe {
|
||||
// create a timer with a 1µs interval (1ns does not work) to mimic polling.
|
||||
// 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
|
||||
// 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
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
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);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
|
||||
@@ -25,7 +25,7 @@ impl From<CursorIcon> for Cursor {
|
||||
CursorIcon::Alias => Cursor::Native("dragLinkCursor"),
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => {
|
||||
Cursor::Native("operationNotAllowedCursor")
|
||||
},
|
||||
}
|
||||
CursorIcon::ContextMenu => Cursor::Native("contextualMenuCursor"),
|
||||
CursorIcon::Crosshair => Cursor::Native("crosshairCursor"),
|
||||
CursorIcon::EResize => Cursor::Native("resizeRightCursor"),
|
||||
@@ -57,7 +57,7 @@ impl From<CursorIcon> for Cursor {
|
||||
// what's used in Safari and Chrome.
|
||||
CursorIcon::Wait | CursorIcon::Progress => {
|
||||
Cursor::Undocumented("busyButClickableCursor")
|
||||
},
|
||||
}
|
||||
|
||||
// For the rest, we can just snatch the cursors from WebKit...
|
||||
// They fit the style of the native cursors, and will seem
|
||||
@@ -81,7 +81,7 @@ impl Cursor {
|
||||
Cursor::Native(cursor_name) => {
|
||||
let sel = Sel::register(cursor_name);
|
||||
msg_send![class!(NSCursor), performSelector: sel]
|
||||
},
|
||||
}
|
||||
Cursor::Undocumented(cursor_name) => {
|
||||
let class = class!(NSCursor);
|
||||
let sel = Sel::register(cursor_name);
|
||||
@@ -92,7 +92,7 @@ impl Cursor {
|
||||
sel!(arrowCursor)
|
||||
};
|
||||
msg_send![class, performSelector: sel]
|
||||
},
|
||||
}
|
||||
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use cocoa::base::{id, nil};
|
||||
|
||||
pub trait IntoOption: Sized {
|
||||
fn into_option(self) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl IntoOption for id {
|
||||
fn into_option(self) -> Option<Self> {
|
||||
match self != nil {
|
||||
true => Some(self),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ lazy_static! {
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(drawRect:),
|
||||
draw_rect as extern "C" fn(&Object, Sel, id),
|
||||
draw_rect as extern "C" fn(&Object, Sel, NSRect),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(acceptsFirstResponder),
|
||||
@@ -280,7 +280,7 @@ extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) {
|
||||
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 {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
@@ -900,7 +900,7 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
|
||||
let phase = match event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
TouchPhase::Started
|
||||
},
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => TouchPhase::Moved,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use raw_window_handle::{macos::MacOSHandle, RawWindowHandle};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
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::{
|
||||
appkit::{
|
||||
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
|
||||
@@ -17,30 +35,12 @@ use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||
};
|
||||
use core_graphics::display::CGDisplay;
|
||||
use core_graphics::display::{CGDisplay, CGDisplayMode};
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
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, 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)]
|
||||
pub struct Id(pub usize);
|
||||
|
||||
@@ -66,6 +66,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub titlebar_buttons_hidden: bool,
|
||||
pub fullsize_content_view: bool,
|
||||
pub resize_increments: Option<LogicalSize>,
|
||||
pub disallow_hidpi: bool,
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
// a window. Changing the layer-backedness of a view breaks the association between
|
||||
@@ -113,11 +119,14 @@ fn create_window(
|
||||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
let screen = match attrs.fullscreen {
|
||||
Some(ref monitor_id) => {
|
||||
let monitor_screen = monitor_id.inner.ns_screen();
|
||||
Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }))
|
||||
| Some(Fullscreen::Exclusive(RootVideoMode {
|
||||
video_mode: VideoMode { ref monitor, .. },
|
||||
})) => {
|
||||
let monitor_screen = monitor.ns_screen();
|
||||
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let frame = match screen {
|
||||
Some(screen) => appkit::NSScreen::frame(screen),
|
||||
@@ -127,7 +136,7 @@ fn create_window(
|
||||
.map(|logical| (logical.width, logical.height))
|
||||
.unwrap_or_else(|| (800.0, 600.0));
|
||||
NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height))
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let mut masks = if !attrs.decorations && !screen.is_some() {
|
||||
@@ -233,12 +242,15 @@ lazy_static! {
|
||||
#[derive(Default)]
|
||||
pub struct SharedState {
|
||||
pub resizable: bool,
|
||||
pub fullscreen: Option<RootMonitorHandle>,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
pub maximized: bool,
|
||||
pub standard_frame: Option<NSRect>,
|
||||
is_simple_fullscreen: bool,
|
||||
pub saved_style: Option<NSWindowStyleMask>,
|
||||
/// Presentation options saved before entering `set_simple_fullscreen`, and
|
||||
/// restored upon exiting it
|
||||
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
||||
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
@@ -301,10 +313,11 @@ impl UnownedWindow {
|
||||
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
|
||||
})?;
|
||||
|
||||
let (ns_view, cursor) = unsafe { create_view(*ns_window) }.ok_or_else(|| {
|
||||
unsafe { pool.drain() };
|
||||
os_error!(OsError::CreationError("Couldn't create `NSView`"))
|
||||
})?;
|
||||
let (ns_view, cursor) =
|
||||
unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
|
||||
unsafe { pool.drain() };
|
||||
os_error!(OsError::CreationError("Couldn't create `NSView`"))
|
||||
})?;
|
||||
|
||||
let input_context = unsafe { util::create_input_context(*ns_view) };
|
||||
|
||||
@@ -355,16 +368,7 @@ impl UnownedWindow {
|
||||
let delegate = new_delegate(&window, fullscreen.is_some());
|
||||
|
||||
// Set fullscreen mode after we setup everything
|
||||
if let Some(monitor) = 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));
|
||||
}
|
||||
window.set_fullscreen(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
|
||||
@@ -594,22 +598,44 @@ 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) {
|
||||
let maximized = {
|
||||
trace!("Locked shared state in `restore_state_from_fullscreen`");
|
||||
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
||||
trace!("Locked shared state in `restore_state_from_fullscreen`");
|
||||
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);
|
||||
shared_state_lock.maximized
|
||||
};
|
||||
drop(shared_state_lock);
|
||||
trace!("Unocked shared state in `restore_state_from_fullscreen`");
|
||||
|
||||
self.set_style_mask_async(mask);
|
||||
self.set_maximized(maximized);
|
||||
}
|
||||
|
||||
fn restore_display_mode(&self) {
|
||||
trace!("Locked shared state in `restore_display_mode`");
|
||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||
|
||||
if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) =
|
||||
shared_state_lock.fullscreen
|
||||
{
|
||||
unsafe {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(
|
||||
ffi::CGDisplayRelease(video_mode.monitor().inner.native_identifier()),
|
||||
ffi::kCGErrorSuccess
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Unlocked shared state in `restore_display_mode`");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let is_zoomed = self.is_zoomed();
|
||||
@@ -627,44 +653,159 @@ impl UnownedWindow {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||
shared_state_lock.fullscreen.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// TODO: Right now set_fullscreen do not work on switching monitors
|
||||
/// in fullscreen mode
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
trace!("Locked shared state in `set_fullscreen`");
|
||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||
if shared_state_lock.is_simple_fullscreen {
|
||||
trace!("Unlocked shared state in `set_fullscreen`");
|
||||
return;
|
||||
}
|
||||
|
||||
let not_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,
|
||||
_ => (),
|
||||
}
|
||||
let old_fullscreen = shared_state_lock.fullscreen.clone();
|
||||
if fullscreen == old_fullscreen {
|
||||
trace!("Unlocked shared state in `set_fullscreen`");
|
||||
current.is_none()
|
||||
};
|
||||
return;
|
||||
}
|
||||
trace!("Unlocked shared state in `set_fullscreen`");
|
||||
drop(shared_state_lock);
|
||||
|
||||
unsafe {
|
||||
util::toggle_full_screen_async(
|
||||
*self.ns_window,
|
||||
*self.ns_view,
|
||||
not_fullscreen,
|
||||
Arc::downgrade(&self.shared_state),
|
||||
)
|
||||
};
|
||||
// If the fullscreen is on a different monitor, we must move the window
|
||||
// to that monitor before we toggle fullscreen (as `toggleFullScreen`
|
||||
// does not take a screen parameter, but uses the current screen)
|
||||
if let Some(ref fullscreen) = fullscreen {
|
||||
let new_screen = match fullscreen {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
(&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(_)), &None) => unsafe {
|
||||
self.restore_display_mode();
|
||||
|
||||
util::toggle_full_screen_async(
|
||||
*self.ns_window,
|
||||
*self.ns_view,
|
||||
old_fullscreen.is_none(),
|
||||
Arc::downgrade(&self.shared_state),
|
||||
);
|
||||
},
|
||||
(&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
|
||||
self.restore_display_mode();
|
||||
}
|
||||
(&None, &Some(Fullscreen::Exclusive(_)))
|
||||
| (&None, &Some(Fullscreen::Borderless(_)))
|
||||
| (&Some(Fullscreen::Borderless(_)), &None) => unsafe {
|
||||
// Wish it were this simple for all cases
|
||||
util::toggle_full_screen_async(
|
||||
*self.ns_window,
|
||||
*self.ns_view,
|
||||
old_fullscreen.is_none(),
|
||||
Arc::downgrade(&self.shared_state),
|
||||
);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
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`");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -764,6 +905,16 @@ impl UnownedWindow {
|
||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
||||
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 {
|
||||
@@ -778,11 +929,13 @@ impl WindowExtMacOS for UnownedWindow {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn request_user_attention(&self, is_critical: bool) {
|
||||
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
|
||||
unsafe {
|
||||
NSApp().requestUserAttention_(match is_critical {
|
||||
true => NSRequestUserAttentionType::NSCriticalRequest,
|
||||
false => NSRequestUserAttentionType::NSInformationalRequest,
|
||||
NSApp().requestUserAttention_(match request_type {
|
||||
RequestUserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
|
||||
RequestUserAttentionType::Informational => {
|
||||
NSRequestUserAttentionType::NSInformationalRequest
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ use std::{
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{self, NSView, NSWindow},
|
||||
appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
|
||||
base::{id, nil},
|
||||
foundation::NSAutoreleasePool,
|
||||
foundation::{NSAutoreleasePool, NSUInteger},
|
||||
};
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
util::{self, IdRef},
|
||||
window::{get_window_id, UnownedWindow},
|
||||
},
|
||||
window::WindowId,
|
||||
window::{Fullscreen, WindowId},
|
||||
};
|
||||
|
||||
pub struct WindowDelegateState {
|
||||
@@ -182,6 +182,11 @@ lazy_static! {
|
||||
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(
|
||||
sel!(windowDidEnterFullScreen:),
|
||||
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
|
||||
@@ -408,6 +413,26 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||
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
|
||||
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `windowDidEnterFullscreen:`");
|
||||
@@ -415,8 +440,21 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||
state.with_window(|window| {
|
||||
let monitor = window.current_monitor();
|
||||
trace!("Locked shared state in `window_did_enter_fullscreen`");
|
||||
window.shared_state.lock().unwrap().fullscreen = Some(monitor);
|
||||
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
|
||||
let mut shared_state = window.shared_state.lock().unwrap();
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#![allow(non_snake_case, unused_unsafe)]
|
||||
|
||||
use std::{
|
||||
mem,
|
||||
os::raw::c_void,
|
||||
sync::{Once, ONCE_INIT},
|
||||
};
|
||||
use std::{mem, os::raw::c_void, sync::Once};
|
||||
|
||||
use winapi::{
|
||||
shared::{
|
||||
@@ -13,13 +9,12 @@ use winapi::{
|
||||
winerror::S_OK,
|
||||
},
|
||||
um::{
|
||||
libloaderapi::{GetProcAddress, LoadLibraryA},
|
||||
shellscalingapi::{
|
||||
MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
},
|
||||
wingdi::{GetDeviceCaps, LOGPIXELSX},
|
||||
winnt::{HRESULT, LPCSTR},
|
||||
winnt::HRESULT,
|
||||
winuser::{self, MONITOR_DEFAULTTONEAREST},
|
||||
},
|
||||
};
|
||||
@@ -39,33 +34,6 @@ type GetDpiForMonitor = unsafe extern "system" fn(
|
||||
) -> HRESULT;
|
||||
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! {
|
||||
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> =
|
||||
get_function!("user32.dll", GetDpiForWindow);
|
||||
@@ -79,7 +47,7 @@ pub fn become_dpi_aware(enable: bool) {
|
||||
if !enable {
|
||||
return;
|
||||
}
|
||||
static ENABLE_DPI_AWARENESS: Once = ONCE_INIT;
|
||||
static ENABLE_DPI_AWARENESS: Once = Once::new();
|
||||
ENABLE_DPI_AWARENESS.call_once(|| {
|
||||
unsafe {
|
||||
if let Some(SetProcessDpiAwarenessContext) =
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
mem,
|
||||
os::windows::ffi::OsStringExt,
|
||||
path::PathBuf,
|
||||
ptr,
|
||||
@@ -11,7 +10,7 @@ use winapi::{
|
||||
ctypes::c_void,
|
||||
shared::{
|
||||
guiddef::REFIID,
|
||||
minwindef::{DWORD, MAX_PATH, UINT, ULONG},
|
||||
minwindef::{DWORD, UINT, ULONG},
|
||||
windef::{HWND, POINTL},
|
||||
winerror::S_OK,
|
||||
},
|
||||
@@ -171,7 +170,6 @@ impl FileDropHandler {
|
||||
F: Fn(PathBuf),
|
||||
{
|
||||
use winapi::{
|
||||
ctypes::wchar_t,
|
||||
shared::{
|
||||
winerror::{DV_E_FORMATETC, SUCCEEDED},
|
||||
wtypes::{CLIPFORMAT, DVASPECT_CONTENT},
|
||||
@@ -191,7 +189,7 @@ impl FileDropHandler {
|
||||
tymed: TYMED_HGLOBAL,
|
||||
};
|
||||
|
||||
let mut medium = mem::uninitialized();
|
||||
let mut medium = std::mem::zeroed();
|
||||
let get_data_result = (*data_obj).GetData(&mut drop_format, &mut medium);
|
||||
if SUCCEEDED(get_data_result) {
|
||||
let hglobal = (*medium.u).hGlobal();
|
||||
@@ -200,15 +198,19 @@ impl FileDropHandler {
|
||||
// The second parameter (0xFFFFFFFF) instructs the function to return the item count
|
||||
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
|
||||
|
||||
let mut pathbuf: [wchar_t; MAX_PATH] = mem::uninitialized();
|
||||
|
||||
for i in 0..item_count {
|
||||
let character_count =
|
||||
DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), MAX_PATH as UINT) as usize;
|
||||
// Get the length of the path string NOT including the terminating null character.
|
||||
// Previously, this was using a fixed size array of MAX_PATH length, but the
|
||||
// Windows API allows longer paths under certain circumstances.
|
||||
let character_count = DragQueryFileW(hdrop, i, ptr::null_mut(), 0) as usize;
|
||||
let str_len = character_count + 1;
|
||||
|
||||
if character_count > 0 {
|
||||
callback(OsString::from_wide(&pathbuf[0..character_count]).into());
|
||||
}
|
||||
// Fill path_buf with the null-terminated file name
|
||||
let mut path_buf = Vec::with_capacity(str_len);
|
||||
DragQueryFileW(hdrop, i, path_buf.as_mut_ptr(), str_len as UINT);
|
||||
path_buf.set_len(str_len);
|
||||
|
||||
callback(OsString::from_wide(&path_buf[0..character_count]).into());
|
||||
}
|
||||
|
||||
return Some(hdrop);
|
||||
|
||||
@@ -285,14 +285,14 @@ pub fn handle_extended_keys(
|
||||
} else {
|
||||
winuser::VK_LCONTROL
|
||||
}
|
||||
},
|
||||
}
|
||||
winuser::VK_MENU => {
|
||||
if extended {
|
||||
winuser::VK_RMENU
|
||||
} else {
|
||||
winuser::VK_LMENU
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
match scancode {
|
||||
// This is only triggered when using raw input. Without this check, we get two events whenever VK_PAUSE is
|
||||
@@ -308,10 +308,10 @@ pub fn handle_extended_keys(
|
||||
} else {
|
||||
winuser::VK_SCROLL
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => vkey,
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
Some((vkey, scancode))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(non_snake_case)]
|
||||
//! 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.
|
||||
@@ -38,7 +39,7 @@ use winapi::{
|
||||
},
|
||||
um::{
|
||||
commctrl, libloaderapi, ole2, processthreadsapi, winbase,
|
||||
winnt::{LONG, LPCSTR, SHORT},
|
||||
winnt::{HANDLE, LONG, LPCSTR, SHORT},
|
||||
winuser,
|
||||
},
|
||||
};
|
||||
@@ -62,6 +63,29 @@ use crate::{
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pub(crate) struct SubclassInput<T> {
|
||||
pub window_state: Arc<Mutex<WindowState>>,
|
||||
pub event_loop_runner: EventLoopRunnerShared<T>,
|
||||
@@ -162,7 +186,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
match event {
|
||||
Some(e) => {
|
||||
runner.process_event(e);
|
||||
},
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
@@ -182,7 +206,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut msg = mem::uninitialized();
|
||||
let mut msg = mem::zeroed();
|
||||
let mut msg_unprocessed = false;
|
||||
|
||||
'main: loop {
|
||||
@@ -195,6 +219,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
|
||||
msg_unprocessed = false;
|
||||
}
|
||||
runner!().events_cleared();
|
||||
@@ -202,19 +227,21 @@ impl<T: 'static> EventLoop<T> {
|
||||
panic::resume_unwind(payload);
|
||||
}
|
||||
|
||||
let control_flow = runner!().control_flow;
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break 'main,
|
||||
ControlFlow::Wait => {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
if !msg_unprocessed {
|
||||
let control_flow = runner!().control_flow;
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break 'main,
|
||||
ControlFlow::Wait => {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
}
|
||||
msg_unprocessed = true;
|
||||
}
|
||||
msg_unprocessed = true;
|
||||
},
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
},
|
||||
ControlFlow::Poll => (),
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
}
|
||||
ControlFlow::Poll => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,6 +280,7 @@ pub(crate) struct EventLoopRunner<T> {
|
||||
runner_state: RunnerState,
|
||||
modal_redraw_window: HWND,
|
||||
in_modal_loop: bool,
|
||||
in_repaint: bool,
|
||||
event_handler: Box<dyn FnMut(Event<T>, &mut ControlFlow)>,
|
||||
panic_error: Option<PanicError>,
|
||||
}
|
||||
@@ -316,6 +344,7 @@ impl<T> EventLoopRunner<T> {
|
||||
control_flow: ControlFlow::default(),
|
||||
runner_state: RunnerState::New,
|
||||
in_modal_loop: false,
|
||||
in_repaint: false,
|
||||
modal_redraw_window: event_loop.window_target.p.thread_msg_target,
|
||||
event_handler: mem::transmute::<
|
||||
Box<dyn FnMut(Event<T>, &mut ControlFlow)>,
|
||||
@@ -335,7 +364,7 @@ impl<T> EventLoopRunner<T> {
|
||||
RunnerState::New => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Init));
|
||||
RunnerState::HandlingEvents
|
||||
},
|
||||
}
|
||||
|
||||
// When `NewEvents` gets sent after an idle depends on the control flow...
|
||||
RunnerState::Idle(wait_start) => {
|
||||
@@ -371,7 +400,7 @@ impl<T> EventLoopRunner<T> {
|
||||
// `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane.
|
||||
ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start),
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -405,27 +434,23 @@ impl<T> EventLoopRunner<T> {
|
||||
start: wait_start,
|
||||
requested_resume: None,
|
||||
}))
|
||||
},
|
||||
}
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
let start_cause = match Instant::now() >= resume_time {
|
||||
// If the current time is later than the requested resume time, the resume time
|
||||
// has been reached.
|
||||
true => {
|
||||
StartCause::ResumeTimeReached {
|
||||
start: wait_start,
|
||||
requested_resume: resume_time,
|
||||
}
|
||||
true => StartCause::ResumeTimeReached {
|
||||
start: wait_start,
|
||||
requested_resume: resume_time,
|
||||
},
|
||||
// Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled.
|
||||
false => {
|
||||
StartCause::WaitCancelled {
|
||||
start: wait_start,
|
||||
requested_resume: Some(resume_time),
|
||||
}
|
||||
false => StartCause::WaitCancelled {
|
||||
start: wait_start,
|
||||
requested_resume: Some(resume_time),
|
||||
},
|
||||
};
|
||||
self.call_event_handler(Event::NewEvents(start_cause));
|
||||
},
|
||||
}
|
||||
// This can be reached if the control flow is changed to poll during a `RedrawRequested`
|
||||
// that was sent after `EventsCleared`.
|
||||
ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)),
|
||||
@@ -433,16 +458,32 @@ impl<T> EventLoopRunner<T> {
|
||||
}
|
||||
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
self.call_event_handler(event);
|
||||
match (self.in_repaint, &event) {
|
||||
(
|
||||
true,
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
},
|
||||
)
|
||||
| (false, _) => self.call_event_handler(event),
|
||||
(true, _) => {
|
||||
self.events_cleared();
|
||||
self.new_events();
|
||||
self.process_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn events_cleared(&mut self) {
|
||||
self.in_repaint = false;
|
||||
|
||||
match self.runner_state {
|
||||
// If we were handling events, send the EventsCleared message.
|
||||
RunnerState::HandlingEvents => {
|
||||
self.call_event_handler(Event::EventsCleared);
|
||||
self.runner_state = RunnerState::Idle(Instant::now());
|
||||
},
|
||||
}
|
||||
|
||||
// If we *weren't* handling events, we don't have to do anything.
|
||||
RunnerState::New | RunnerState::Idle(..) => (),
|
||||
@@ -455,7 +496,7 @@ impl<T> EventLoopRunner<T> {
|
||||
ControlFlow::Poll => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Poll));
|
||||
self.call_event_handler(Event::EventsCleared);
|
||||
},
|
||||
}
|
||||
// If we had deferred a WaitUntil and the resume time has since been reached,
|
||||
// send the resume notification and EventsCleared event.
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
@@ -468,27 +509,29 @@ impl<T> EventLoopRunner<T> {
|
||||
));
|
||||
self.call_event_handler(Event::EventsCleared);
|
||||
}
|
||||
},
|
||||
}
|
||||
// If we deferred a wait and no events were received, the user doesn't have to
|
||||
// get an event.
|
||||
ControlFlow::Wait | ControlFlow::Exit => (),
|
||||
}
|
||||
// Mark that we've entered an idle state.
|
||||
self.runner_state = RunnerState::Idle(wait_start)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call_event_handler(&mut self, event: Event<T>) {
|
||||
match event {
|
||||
Event::NewEvents(_) => {
|
||||
self.trigger_newevents_on_redraw
|
||||
.store(true, Ordering::Relaxed)
|
||||
},
|
||||
Event::EventsCleared => {
|
||||
self.trigger_newevents_on_redraw
|
||||
.store(false, Ordering::Relaxed)
|
||||
},
|
||||
Event::NewEvents(_) => self
|
||||
.trigger_newevents_on_redraw
|
||||
.store(true, Ordering::Relaxed),
|
||||
Event::EventsCleared => self
|
||||
.trigger_newevents_on_redraw
|
||||
.store(false, Ordering::Relaxed),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => self.in_repaint = true,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -513,7 +556,7 @@ impl<T> EventLoopRunner<T> {
|
||||
|
||||
// Returns true if the wait time was reached, and false if a message must be processed.
|
||||
unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool {
|
||||
let mut msg = mem::uninitialized();
|
||||
let mut msg = mem::zeroed();
|
||||
let now = Instant::now();
|
||||
if now <= wait_until {
|
||||
// MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond
|
||||
@@ -635,13 +678,21 @@ impl EventLoopThreadExecutor {
|
||||
|
||||
type ThreadExecFn = Box<Box<dyn FnMut()>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
target_window: HWND,
|
||||
event_send: Sender<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> {
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
|
||||
unsafe {
|
||||
@@ -825,18 +876,18 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
runner.in_modal_loop = true;
|
||||
}
|
||||
0
|
||||
},
|
||||
}
|
||||
winuser::WM_EXITSIZEMOVE => {
|
||||
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner {
|
||||
runner.in_modal_loop = false;
|
||||
}
|
||||
0
|
||||
},
|
||||
}
|
||||
winuser::WM_NCCREATE => {
|
||||
enable_non_client_dpi_scaling(window);
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_NCLBUTTONDOWN => {
|
||||
// jumpstart the modal loop
|
||||
@@ -850,7 +901,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
|
||||
}
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_CLOSE => {
|
||||
use crate::event::WindowEvent::CloseRequested;
|
||||
@@ -859,7 +910,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
event: CloseRequested,
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_DESTROY => {
|
||||
use crate::event::WindowEvent::Destroyed;
|
||||
@@ -872,17 +923,17 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
Box::from_raw(subclass_input);
|
||||
drop(subclass_input);
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
_ if msg == *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID => {
|
||||
use crate::event::WindowEvent::RedrawRequested;
|
||||
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
|
||||
subclass_input.window_state.lock().queued_out_of_band_redraw = false;
|
||||
if let Some(ref mut runner) = *runner {
|
||||
// This check makes sure that calls to `request_redraw()` during `EventsCleared`
|
||||
// handling dispatch `RedrawRequested` immediately after `EventsCleared`, without
|
||||
// spinning up a new event loop iteration. We do this because that's what the API
|
||||
// says to do.
|
||||
let control_flow = runner.control_flow;
|
||||
let runner_state = runner.runner_state;
|
||||
let mut request_redraw = || {
|
||||
runner.call_event_handler(Event::WindowEvent {
|
||||
@@ -893,21 +944,18 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
match runner_state {
|
||||
RunnerState::Idle(..) | RunnerState::DeferredNewEvents(..) => request_redraw(),
|
||||
RunnerState::HandlingEvents => {
|
||||
match control_flow {
|
||||
ControlFlow::Poll => request_redraw(),
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
if resume_time <= Instant::now() {
|
||||
request_redraw()
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
winuser::RedrawWindow(
|
||||
window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
0
|
||||
},
|
||||
}
|
||||
winuser::WM_PAINT => {
|
||||
use crate::event::WindowEvent::RedrawRequested;
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
@@ -915,7 +963,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
event: RedrawRequested,
|
||||
});
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
},
|
||||
}
|
||||
|
||||
// WM_MOVE supplies client area positions, so we send Moved here instead.
|
||||
winuser::WM_WINDOWPOSCHANGED => {
|
||||
@@ -934,7 +982,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
|
||||
// This is necessary for us to still get sent WM_SIZE.
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_SIZE => {
|
||||
use crate::event::WindowEvent::Resized;
|
||||
@@ -962,7 +1010,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
|
||||
subclass_input.send_event(event);
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_CHAR => {
|
||||
use crate::event::WindowEvent::ReceivedCharacter;
|
||||
@@ -972,7 +1020,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
event: ReceivedCharacter(chr),
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
// Prevents default windows menu hotkeys playing unwanted
|
||||
// "ding" sounds. Alternatively could check for WM_SYSCOMMAND
|
||||
@@ -980,6 +1028,17 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
// other unwanted default hotkeys as well.
|
||||
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 => {
|
||||
use crate::event::WindowEvent::{CursorEntered, CursorMoved};
|
||||
let mouse_was_outside_window = {
|
||||
@@ -1024,7 +1083,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_MOUSELEAVE => {
|
||||
use crate::event::WindowEvent::CursorLeft;
|
||||
@@ -1043,7 +1102,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_MOUSEWHEEL => {
|
||||
use crate::event::MouseScrollDelta::LineDelta;
|
||||
@@ -1063,7 +1122,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_MOUSEHWHEEL => {
|
||||
use crate::event::MouseScrollDelta::LineDelta;
|
||||
@@ -1083,7 +1142,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => {
|
||||
use crate::event::{ElementState::Pressed, VirtualKeyCode};
|
||||
@@ -1114,7 +1173,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
}
|
||||
0
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_KEYUP | winuser::WM_SYSKEYUP => {
|
||||
use crate::event::ElementState::Released;
|
||||
@@ -1133,7 +1192,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
}
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_LBUTTONDOWN => {
|
||||
use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput};
|
||||
@@ -1150,7 +1209,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_LBUTTONUP => {
|
||||
use crate::event::{
|
||||
@@ -1169,7 +1228,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_RBUTTONDOWN => {
|
||||
use crate::event::{
|
||||
@@ -1188,7 +1247,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_RBUTTONUP => {
|
||||
use crate::event::{
|
||||
@@ -1207,7 +1266,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_MBUTTONDOWN => {
|
||||
use crate::event::{
|
||||
@@ -1226,7 +1285,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_MBUTTONUP => {
|
||||
use crate::event::{
|
||||
@@ -1245,7 +1304,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_XBUTTONDOWN => {
|
||||
use crate::event::{
|
||||
@@ -1265,7 +1324,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_XBUTTONUP => {
|
||||
use crate::event::{
|
||||
@@ -1285,7 +1344,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
},
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_INPUT_DEVICE_CHANGE => {
|
||||
let event = match wparam as _ {
|
||||
@@ -1300,7 +1359,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_INPUT => {
|
||||
use crate::event::{
|
||||
@@ -1399,7 +1458,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
}
|
||||
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_TOUCH => {
|
||||
let pcount = LOWORD(wparam as DWORD) as usize;
|
||||
@@ -1415,8 +1474,17 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
{
|
||||
let dpi_factor = hwnd_scale_factor(window);
|
||||
for input in &inputs {
|
||||
let x = (input.x as f64) / 100f64;
|
||||
let y = (input.y as f64) / 100f64;
|
||||
let mut location = POINT {
|
||||
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);
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
@@ -1431,6 +1499,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
continue;
|
||||
},
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: input.dwID as u64,
|
||||
device_id: DEVICE_ID,
|
||||
}),
|
||||
@@ -1439,7 +1508,113 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
}
|
||||
winuser::CloseTouchInputHandle(htouch);
|
||||
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: RECT = mem::uninitialized();
|
||||
let mut display_rect: RECT = mem::uninitialized();
|
||||
|
||||
if (GetPointerDeviceRects(
|
||||
pointer_info.sourceDevice,
|
||||
&mut device_rect as *mut _,
|
||||
&mut display_rect as *mut _,
|
||||
)) == 0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 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: None, // TODO
|
||||
id: pointer_info.pointerId as u64,
|
||||
device_id: DEVICE_ID,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
SkipPointerFrameMessages(pointer_id);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
winuser::WM_SETFOCUS => {
|
||||
use crate::event::WindowEvent::Focused;
|
||||
@@ -1449,7 +1624,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_KILLFOCUS => {
|
||||
use crate::event::WindowEvent::Focused;
|
||||
@@ -1458,7 +1633,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
event: Focused(false),
|
||||
});
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_SETCURSOR => {
|
||||
let set_cursor_to = {
|
||||
@@ -1479,15 +1654,15 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
let cursor = winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor());
|
||||
winuser::SetCursor(cursor);
|
||||
0
|
||||
},
|
||||
}
|
||||
None => winuser::DefWindowProcW(window, msg, wparam, lparam),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_DROPFILES => {
|
||||
// See `FileDropHandler` for implementation.
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
winuser::WM_GETMINMAXINFO => {
|
||||
let mmi = lparam as *mut winuser::MINMAXINFO;
|
||||
@@ -1516,7 +1691,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
}
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
// Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change
|
||||
// DPI, therefore all applications are closed while DPI is changing.
|
||||
@@ -1560,7 +1735,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
});
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
|
||||
_ => {
|
||||
if msg == *DESTROY_MSG_ID {
|
||||
@@ -1613,7 +1788,7 @@ unsafe extern "system" fn public_window_callback<T>(
|
||||
} else {
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1631,7 +1806,7 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
||||
Box::from_raw(subclass_input);
|
||||
drop(subclass_input);
|
||||
0
|
||||
},
|
||||
}
|
||||
// Because WM_PAINT comes after all other messages, we use it during modal loops to detect
|
||||
// when the event queue has been emptied. See `process_event` for more details.
|
||||
winuser::WM_PAINT => {
|
||||
@@ -1653,7 +1828,7 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
||||
}
|
||||
};
|
||||
if in_modal_loop {
|
||||
let mut msg = mem::uninitialized();
|
||||
let mut msg = mem::zeroed();
|
||||
loop {
|
||||
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
|
||||
break;
|
||||
@@ -1669,13 +1844,13 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
},
|
||||
}
|
||||
// If the message isn't one of those three, it may be handled by the modal
|
||||
// loop so we should return control flow to it.
|
||||
_ => {
|
||||
queue_call_again();
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1689,27 +1864,27 @@ unsafe extern "system" fn thread_event_target_callback<T>(
|
||||
wait_until_time_or_msg(resume_time);
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
},
|
||||
}
|
||||
ControlFlow::Poll => {
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
},
|
||||
}
|
||||
_ if msg == *USER_EVENT_MSG_ID => {
|
||||
if let Ok(event) = subclass_input.user_event_receiver.recv() {
|
||||
subclass_input.send_event(Event::UserEvent(event));
|
||||
}
|
||||
0
|
||||
},
|
||||
}
|
||||
_ if msg == *EXEC_MSG_ID => {
|
||||
let mut function: ThreadExecFn = Box::from_raw(wparam as usize as *mut _);
|
||||
function();
|
||||
0
|
||||
},
|
||||
}
|
||||
_ => commctrl::DefSubclassProc(window, msg, wparam, lparam),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND};
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
@@ -67,6 +67,8 @@ impl WindowId {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
mod dpi;
|
||||
mod drop_handler;
|
||||
mod event;
|
||||
@@ -74,6 +76,5 @@ mod event_loop;
|
||||
mod icon;
|
||||
mod monitor;
|
||||
mod raw_input;
|
||||
mod util;
|
||||
mod window;
|
||||
mod window_state;
|
||||
|
||||
@@ -3,54 +3,64 @@ use winapi::{
|
||||
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
|
||||
windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
|
||||
},
|
||||
um::{wingdi, winnt::LONG, winuser},
|
||||
um::{wingdi, winuser},
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
collections::{BTreeSet, VecDeque},
|
||||
io, mem, ptr,
|
||||
};
|
||||
|
||||
use super::{util, EventLoop};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::VideoMode,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::{
|
||||
dpi::{dpi_to_scale_factor, get_monitor_dpi},
|
||||
window::Window,
|
||||
},
|
||||
};
|
||||
|
||||
/// Win32 implementation of the main `MonitorHandle` object.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug, Clone)]
|
||||
pub struct MonitorHandle {
|
||||
/// Monitor handle.
|
||||
hmonitor: HMonitor,
|
||||
#[derivative(Debug = "ignore")]
|
||||
monitor_info: winuser::MONITORINFOEXW,
|
||||
/// The system name of the monitor.
|
||||
monitor_name: String,
|
||||
/// 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,
|
||||
#[derivative(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct VideoMode {
|
||||
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_video_mode: wingdi::DEVMODEW,
|
||||
}
|
||||
|
||||
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.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// 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(
|
||||
hmonitor: HMONITOR,
|
||||
@@ -59,7 +69,7 @@ unsafe extern "system" fn monitor_enum_proc(
|
||||
data: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = data as *mut VecDeque<MonitorHandle>;
|
||||
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor));
|
||||
(*monitors).push_back(MonitorHandle::new(hmonitor));
|
||||
TRUE // continue enumeration
|
||||
}
|
||||
|
||||
@@ -79,12 +89,12 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
pub fn primary_monitor() -> MonitorHandle {
|
||||
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
||||
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
|
||||
MonitorHandle::from_hmonitor(hmonitor)
|
||||
MonitorHandle::new(hmonitor)
|
||||
}
|
||||
|
||||
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
|
||||
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
|
||||
MonitorHandle::from_hmonitor(hmonitor)
|
||||
MonitorHandle::new(hmonitor)
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
@@ -109,7 +119,7 @@ impl Window {
|
||||
}
|
||||
|
||||
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, io::Error> {
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() };
|
||||
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::zeroed() };
|
||||
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
|
||||
let status = unsafe {
|
||||
winuser::GetMonitorInfoW(
|
||||
@@ -125,73 +135,69 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINF
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
|
||||
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
|
||||
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 new(hmonitor: HMONITOR) -> Self {
|
||||
MonitorHandle(hmonitor)
|
||||
}
|
||||
|
||||
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
||||
let left = self.position.0 as LONG;
|
||||
let right = left + self.dimensions.0 as LONG;
|
||||
let top = self.position.1 as LONG;
|
||||
let bottom = top + self.dimensions.1 as LONG;
|
||||
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
|
||||
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||
point.x >= monitor_info.rcMonitor.left
|
||||
&& point.x <= monitor_info.rcMonitor.right
|
||||
&& point.y >= monitor_info.rcMonitor.top
|
||||
&& point.y <= monitor_info.rcMonitor.bottom
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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]
|
||||
pub fn native_identifier(&self) -> String {
|
||||
self.monitor_name.clone()
|
||||
self.name().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hmonitor(&self) -> HMONITOR {
|
||||
self.hmonitor.0
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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]
|
||||
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]
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
|
||||
}
|
||||
|
||||
#[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
|
||||
// fields are probably changing, but we aren't looking at those fields
|
||||
// anyway), so we're using a HashSet deduplicate
|
||||
let mut modes = HashSet::new();
|
||||
// anyway), so we're using a BTreeSet deduplicate
|
||||
let mut modes = BTreeSet::new();
|
||||
let mut i = 0;
|
||||
|
||||
loop {
|
||||
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();
|
||||
mode.dmSize = mem::size_of_val(&mode) as WORD;
|
||||
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
|
||||
@@ -205,10 +211,14 @@ impl MonitorHandle {
|
||||
| wingdi::DM_DISPLAYFREQUENCY;
|
||||
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
|
||||
|
||||
modes.insert(VideoMode {
|
||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: mode.dmBitsPerPel as u16,
|
||||
refresh_rate: mode.dmDisplayFrequency as u16,
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode {
|
||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: mode.dmBitsPerPel as u16,
|
||||
refresh_rate: mode.dmDisplayFrequency as u16,
|
||||
monitor: self.clone(),
|
||||
native_video_mode: mode,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ impl From<RID_DEVICE_INFO> for RawDeviceInfo {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
|
||||
let mut info: RID_DEVICE_INFO = unsafe { mem::uninitialized() };
|
||||
let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() };
|
||||
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
|
||||
|
||||
info.cbSize = info_size;
|
||||
@@ -164,7 +164,7 @@ pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> boo
|
||||
}
|
||||
|
||||
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RAWINPUT> {
|
||||
let mut data: RAWINPUT = unsafe { mem::uninitialized() };
|
||||
let mut data: RAWINPUT = unsafe { mem::zeroed() };
|
||||
let mut data_size = size_of::<RAWINPUT>() as UINT;
|
||||
let header_size = size_of::<RAWINPUTHEADER>() as UINT;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
io, mem,
|
||||
ops::BitAnd,
|
||||
os::raw::c_void,
|
||||
ptr, slice,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
@@ -12,9 +13,44 @@ use winapi::{
|
||||
minwindef::{BOOL, DWORD},
|
||||
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
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
@@ -33,7 +69,7 @@ pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String {
|
||||
}
|
||||
|
||||
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> {
|
||||
let mut data: T = mem::uninitialized();
|
||||
let mut data: T = mem::zeroed();
|
||||
if fun(&mut data) != 0 {
|
||||
Some(data)
|
||||
} else {
|
||||
@@ -59,7 +95,7 @@ pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
|
||||
|
||||
pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
|
||||
unsafe {
|
||||
let mut rect = mem::uninitialized();
|
||||
let mut rect = mem::zeroed();
|
||||
let mut top_left = mem::zeroed();
|
||||
|
||||
win_to_err(|| winuser::ClientToScreen(hwnd, &mut top_left))?;
|
||||
@@ -129,7 +165,7 @@ impl CursorIcon {
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => winuser::IDC_NO,
|
||||
CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => {
|
||||
winuser::IDC_SIZEALL
|
||||
},
|
||||
}
|
||||
CursorIcon::EResize
|
||||
| CursorIcon::WResize
|
||||
| CursorIcon::EwResize
|
||||
@@ -140,10 +176,10 @@ impl CursorIcon {
|
||||
| CursorIcon::RowResize => winuser::IDC_SIZENS,
|
||||
CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => {
|
||||
winuser::IDC_SIZENESW
|
||||
},
|
||||
}
|
||||
CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => {
|
||||
winuser::IDC_SIZENWSE
|
||||
},
|
||||
}
|
||||
CursorIcon::Wait => winuser::IDC_WAIT,
|
||||
CursorIcon::Progress => winuser::IDC_APPSTARTING,
|
||||
CursorIcon::Help => winuser::IDC_HELP,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::{windows::WindowsHandle, RawWindowHandle};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
ffi::OsStr,
|
||||
@@ -46,7 +47,7 @@ use crate::{
|
||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||
PlatformSpecificWindowBuilderAttributes, WindowId,
|
||||
},
|
||||
window::{CursorIcon, Icon, WindowAttributes},
|
||||
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
|
||||
};
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
@@ -149,7 +150,11 @@ impl Window {
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
} else {
|
||||
winuser::PostMessageW(self.window.0, *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, 0, 0);
|
||||
let mut window_state = self.window_state.lock();
|
||||
if !window_state.queued_out_of_band_redraw {
|
||||
window_state.queued_out_of_band_redraw = true;
|
||||
winuser::PostMessageW(self.window.0, *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,7 +216,7 @@ impl Window {
|
||||
}
|
||||
|
||||
pub(crate) fn inner_size_physical(&self) -> (u32, u32) {
|
||||
let mut rect: RECT = unsafe { mem::uninitialized() };
|
||||
let mut rect: RECT = unsafe { mem::zeroed() };
|
||||
if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 {
|
||||
panic!("Unexpected GetClientRect failure: please report this error to https://github.com/rust-windowing/winit")
|
||||
}
|
||||
@@ -271,7 +276,8 @@ impl Window {
|
||||
winuser::SWP_ASYNCWINDOWPOS
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOREPOSITION
|
||||
| winuser::SWP_NOMOVE,
|
||||
| winuser::SWP_NOMOVE
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
@@ -322,7 +328,7 @@ impl Window {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
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)
|
||||
});
|
||||
});
|
||||
@@ -334,6 +340,15 @@ impl Window {
|
||||
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]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
self.window_state.lock().mouse.cursor = cursor;
|
||||
@@ -416,80 +431,177 @@ impl Window {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
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)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
let window_state = self.window_state.lock();
|
||||
window_state.fullscreen.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
||||
unsafe {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
match &monitor {
|
||||
&Some(RootMonitorHandle { ref inner }) => {
|
||||
let (x, y): (i32, i32) = inner.position().into();
|
||||
let (width, height): (u32, u32) = inner.size().into();
|
||||
|
||||
let mut monitor = monitor.clone();
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let mut window_state_lock = window_state.lock();
|
||||
|
||||
let client_rect =
|
||||
util::get_client_rect(window.0).expect("get client rect failed!");
|
||||
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 {
|
||||
client_rect,
|
||||
dpi_factor,
|
||||
}) = window_state_lock.saved_window
|
||||
{
|
||||
window_state_lock.dpi_factor = dpi_factor;
|
||||
window_state_lock.saved_window = None;
|
||||
|
||||
WindowState::refresh_window_state(
|
||||
window_state_lock,
|
||||
window.0,
|
||||
Some(client_rect),
|
||||
);
|
||||
}
|
||||
|
||||
mark_fullscreen(window.0, false);
|
||||
});
|
||||
},
|
||||
}
|
||||
let mut window_state_lock = window_state.lock();
|
||||
let old_fullscreen = window_state_lock.fullscreen.clone();
|
||||
if window_state_lock.fullscreen == fullscreen {
|
||||
return;
|
||||
}
|
||||
window_state_lock.fullscreen = fullscreen.clone();
|
||||
drop(window_state_lock);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let mut window_state_lock = window_state.lock();
|
||||
|
||||
// Save window bounds before entering fullscreen
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
(&None, &Some(_)) => {
|
||||
let client_rect = util::get_client_rect(window.0).unwrap();
|
||||
window_state_lock.saved_window = Some(SavedWindow {
|
||||
client_rect,
|
||||
dpi_factor: window_state_lock.dpi_factor,
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Change video mode if we're transitioning to or from exclusive
|
||||
// fullscreen
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
(&None, &Some(Fullscreen::Exclusive(ref video_mode)))
|
||||
| (
|
||||
&Some(Fullscreen::Borderless(_)),
|
||||
&Some(Fullscreen::Exclusive(ref video_mode)),
|
||||
)
|
||||
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) =>
|
||||
{
|
||||
let monitor = video_mode.monitor();
|
||||
|
||||
let mut display_name = OsStr::new(&monitor.inner.native_identifier())
|
||||
.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);
|
||||
|
||||
unsafe {
|
||||
winuser::SetWindowPos(
|
||||
window.0,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -498,8 +610,7 @@ impl Window {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
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, Some(client_rect), |f| {
|
||||
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||
f.set(WindowFlags::DECORATIONS, decorations)
|
||||
});
|
||||
});
|
||||
@@ -511,7 +622,7 @@ impl Window {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
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)
|
||||
});
|
||||
});
|
||||
@@ -764,9 +875,7 @@ unsafe fn init<T: 'static>(
|
||||
let window_state = {
|
||||
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
|
||||
let window_state = Arc::new(Mutex::new(window_state));
|
||||
WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| {
|
||||
*f = window_flags
|
||||
});
|
||||
WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
|
||||
window_state
|
||||
};
|
||||
|
||||
@@ -860,7 +969,7 @@ pub fn com_initialized() {
|
||||
// is activated. If the window is not fullscreen, the Shell falls back to
|
||||
// heuristics to determine how the window should be treated, which means
|
||||
// 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();
|
||||
|
||||
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::platform::{event_loop, icon::WinIcon, util},
|
||||
window::{CursorIcon, WindowAttributes},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
use parking_lot::MutexGuard;
|
||||
use std::{io, ptr};
|
||||
@@ -29,7 +28,10 @@ pub struct WindowState {
|
||||
pub saved_window: Option<SavedWindow>,
|
||||
pub dpi_factor: f64,
|
||||
|
||||
pub fullscreen: Option<MonitorHandle>,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
|
||||
/// times in `EventsCleared`.
|
||||
pub queued_out_of_band_redraw: bool,
|
||||
window_flags: WindowFlags,
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ bitflags! {
|
||||
WindowFlags::RESIZABLE.bits |
|
||||
WindowFlags::MAXIMIZED.bits
|
||||
);
|
||||
const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
|
||||
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
||||
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
||||
}
|
||||
@@ -110,6 +113,7 @@ impl WindowState {
|
||||
dpi_factor,
|
||||
|
||||
fullscreen: None,
|
||||
queued_out_of_band_redraw: false,
|
||||
window_flags: WindowFlags::empty(),
|
||||
}
|
||||
}
|
||||
@@ -118,32 +122,16 @@ impl WindowState {
|
||||
self.window_flags
|
||||
}
|
||||
|
||||
pub fn set_window_flags<F>(
|
||||
mut this: MutexGuard<'_, Self>,
|
||||
window: HWND,
|
||||
set_client_rect: Option<RECT>,
|
||||
f: F,
|
||||
) where
|
||||
pub fn set_window_flags<F>(mut this: MutexGuard<'_, Self>, window: HWND, f: F)
|
||||
where
|
||||
F: FnOnce(&mut WindowFlags),
|
||||
{
|
||||
let old_flags = 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;
|
||||
|
||||
drop(this);
|
||||
old_flags.apply_diff(window, new_flags, set_client_rect);
|
||||
}
|
||||
|
||||
pub fn refresh_window_state(
|
||||
this: MutexGuard<'_, Self>,
|
||||
window: HWND,
|
||||
set_client_rect: Option<RECT>,
|
||||
) {
|
||||
Self::set_window_flags(this, window, set_client_rect, |_| ());
|
||||
old_flags.apply_diff(window, new_flags);
|
||||
}
|
||||
|
||||
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
||||
@@ -170,7 +158,7 @@ impl MouseProperties {
|
||||
Err(e) => {
|
||||
self.cursor_flags = old_flags;
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -181,6 +169,7 @@ impl WindowFlags {
|
||||
fn mask(mut self) -> WindowFlags {
|
||||
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
||||
self |= WindowFlags::FULLSCREEN_OR_MASK;
|
||||
}
|
||||
if !self.contains(WindowFlags::VISIBLE) {
|
||||
self &= WindowFlags::INVISIBLE_AND_MASK;
|
||||
@@ -215,8 +204,9 @@ impl WindowFlags {
|
||||
if self.contains(WindowFlags::NO_BACK_BUFFER) {
|
||||
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
|
||||
}
|
||||
// if self.contains(WindowFlags::TRANSPARENT) {
|
||||
// }
|
||||
if self.contains(WindowFlags::TRANSPARENT) && self.contains(WindowFlags::DECORATIONS) {
|
||||
style_ex |= WS_EX_LAYERED;
|
||||
}
|
||||
if self.contains(WindowFlags::CHILD) {
|
||||
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
|
||||
}
|
||||
@@ -231,7 +221,7 @@ impl WindowFlags {
|
||||
}
|
||||
|
||||
/// 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();
|
||||
new = new.mask();
|
||||
|
||||
@@ -290,42 +280,20 @@ impl WindowFlags {
|
||||
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
|
||||
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
|
||||
|
||||
match set_client_rect
|
||||
.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r))
|
||||
{
|
||||
Some(client_rect) => {
|
||||
let (x, y, w, h) = (
|
||||
client_rect.left,
|
||||
client_rect.top,
|
||||
client_rect.right - client_rect.left,
|
||||
client_rect.bottom - client_rect.top,
|
||||
);
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
ptr::null_mut(),
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
winuser::SWP_NOZORDER | winuser::SWP_FRAMECHANGED,
|
||||
);
|
||||
},
|
||||
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,
|
||||
);
|
||||
},
|
||||
let mut flags = winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOMOVE
|
||||
| winuser::SWP_NOSIZE
|
||||
| winuser::SWP_FRAMECHANGED;
|
||||
|
||||
// We generally don't want style changes here to affect window
|
||||
// focus, but for fullscreen windows they must be activated
|
||||
// (i.e. focused) so that they appear on top of the taskbar
|
||||
if !new.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||
flags |= 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
error::{ExternalError, NotSupportedError, OsError},
|
||||
event_loop::EventLoopWindowTarget,
|
||||
monitor::{AvailableMonitorsIter, MonitorHandle},
|
||||
monitor::{AvailableMonitorsIter, MonitorHandle, VideoMode},
|
||||
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.
|
||||
///
|
||||
/// Can be obtained with `window.id()`.
|
||||
@@ -71,7 +83,7 @@ pub struct WindowBuilder {
|
||||
/// The attributes to use to create the window.
|
||||
pub window: WindowAttributes,
|
||||
|
||||
// Platform-specific configuration. Private.
|
||||
// Platform-specific configuration.
|
||||
pub(crate) platform_specific: platform_impl::PlatformSpecificWindowBuilderAttributes,
|
||||
}
|
||||
|
||||
@@ -110,7 +122,7 @@ pub struct WindowAttributes {
|
||||
/// Whether the window should be set as fullscreen upon creation.
|
||||
///
|
||||
/// The default is `None`.
|
||||
pub fullscreen: Option<MonitorHandle>,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
///
|
||||
@@ -222,10 +234,14 @@ impl WindowBuilder {
|
||||
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
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
||||
#[inline]
|
||||
pub fn with_fullscreen(mut self, monitor: Option<MonitorHandle>) -> WindowBuilder {
|
||||
pub fn with_fullscreen(mut self, monitor: Option<Fullscreen>) -> WindowBuilder {
|
||||
self.window.fullscreen = monitor;
|
||||
self
|
||||
}
|
||||
@@ -285,24 +301,12 @@ impl WindowBuilder {
|
||||
|
||||
/// Builds the window.
|
||||
///
|
||||
/// Error should be very rare and only occur in case of permission denied, incompatible system,
|
||||
/// out of memory, etc.
|
||||
/// Possible causes of error include denied permission, incompatible system, and lack of memory.
|
||||
#[inline]
|
||||
pub fn build<T: 'static>(
|
||||
mut self,
|
||||
self,
|
||||
window_target: &EventLoopWindowTarget<T>,
|
||||
) -> Result<Window, OsError> {
|
||||
self.window.inner_size = Some(self.window.inner_size.unwrap_or_else(|| {
|
||||
if let Some(ref monitor) = self.window.fullscreen {
|
||||
// resizing the window to the dimensions of the monitor when fullscreen
|
||||
LogicalSize::from_physical(monitor.size(), monitor.hidpi_factor()) // DPI factor applies here since this is a borderless window and not real fullscreen
|
||||
} else {
|
||||
// default dimensions
|
||||
(1024, 768).into()
|
||||
}
|
||||
}));
|
||||
|
||||
// building
|
||||
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
|
||||
.map(|window| Window { window })
|
||||
}
|
||||
@@ -330,7 +334,7 @@ impl Window {
|
||||
|
||||
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
///
|
||||
/// See the [`dpi`](dpi/index.html) module for more information.
|
||||
/// See the [`dpi`](../dpi/index.html) module for more information.
|
||||
///
|
||||
/// Note that this value can change depending on user action (for example if the window is
|
||||
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
|
||||
@@ -544,10 +548,27 @@ impl Window {
|
||||
///
|
||||
/// ## 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.
|
||||
/// - **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]
|
||||
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) {
|
||||
self.window.set_fullscreen(monitor)
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
self.window.set_fullscreen(fullscreen)
|
||||
}
|
||||
|
||||
/// Gets the window's current fullscreen state.
|
||||
@@ -556,7 +577,7 @@ impl Window {
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<MonitorHandle> {
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
self.window.fullscreen()
|
||||
}
|
||||
|
||||
@@ -564,10 +585,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden
|
||||
/// via [`setPrefersStatusBarHidden`].
|
||||
///
|
||||
/// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc
|
||||
/// - **iOS:** Has no effect.
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
self.window.set_decorations(decorations)
|
||||
@@ -703,6 +721,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.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
@@ -765,3 +789,9 @@ impl Default for CursorIcon {
|
||||
CursorIcon::Default
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Fullscreen {
|
||||
Exclusive(VideoMode),
|
||||
Borderless(MonitorHandle),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user