Merge branch 'master' into merge-master-to-web

This commit is contained in:
Ryan Goldstein 2019-09-24 14:21:18 -04:00
commit 3e8669ea7f
70 changed files with 4797 additions and 3676 deletions

View file

@ -1,4 +1,5 @@
- [ ] Tested on all platforms changed - [ ] Tested on all platforms changed
- [ ] Compilation warnings were addressed
- [ ] `cargo fmt` has been run on this branch - [ ] `cargo fmt` has been run on this branch
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users - [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior - [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior

View file

@ -34,7 +34,7 @@ matrix:
os: osx os: osx
rust: stable rust: stable
# iOS # iOS x86_64
- env: TARGET=x86_64-apple-ios - env: TARGET=x86_64-apple-ios
os: osx os: osx
rust: nightly rust: nightly
@ -42,18 +42,35 @@ matrix:
os: osx os: osx
rust: stable rust: stable
# iOS armv7
- env: TARGET=armv7-apple-ios
os: osx
rust: nightly
- env: TARGET=armv7-apple-ios
os: osx
rust: stable
# iOS arm64
- env: TARGET=aarch64-apple-ios
os: osx
rust: nightly
- env: TARGET=aarch64-apple-ios
os: osx
rust: stable
install: install:
- rustup self update - rustup self update
- rustup target add $TARGET; true - rustup target add $TARGET; true
- rustup component add rustfmt - rustup toolchain install stable
- rustup component add rustfmt --toolchain stable
script: script:
- cargo fmt --all -- --check - cargo +stable fmt --all -- --check
- cargo build --target $TARGET --verbose - cargo build --target $TARGET --verbose
- cargo build --target $TARGET --features serde --verbose - cargo build --target $TARGET --features serde --verbose
# Running iOS apps on OSX requires the simulator so we skip that for now # Running iOS apps on macOS requires the Simulator so we skip that for now
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi - if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --verbose; fi
- if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi - if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --features serde --verbose; fi
after_success: after_success:
- | - |

View file

@ -1,5 +1,59 @@
# Unreleased # Unreleased
- On macOS, implement `run_return`.
- On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`.
- On X11, performance is improved when rapidly calling `Window::set_cursor_icon`.
- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized.
- On Windows, unset `maximized` when manually changing the window's position or size.
- On Windows, add touch pressure information for touch events.
- On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`.
- On Wayland, fix event processing sometimes stalling when using OpenGL with vsync.
- Officially remove the Emscripten backend.
- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`.
- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode.
- On X11, allow setting mulitple `XWindowType`s.
- On iOS, fix null window on initial `HiDpiFactorChanged` event.
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized
by the user.
- On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events.
- On X11, return dummy monitor data to avoid panicking when no monitors exist.
- On X11, prevent stealing input focus when creating a new window.
Only steal input focus when entering fullscreen mode.
- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced
# 0.20.0 Alpha 3 (2019-08-14)
- On macOS, drop the run closure on exit.
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
- On X11, fix delayed events after window redraw.
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
- On Windows, screen saver won't start if the window is in fullscreen mode.
- Change all occurrences of the `new_user_event` method to `with_user_event`.
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants.
- Adds support for exclusive fullscreen mode.
- On iOS, add support for hiding the home indicator.
- On iOS, add support for deferring system gestures.
- On iOS, fix a crash that occurred while acquiring a monitor's name.
- On iOS, fix armv7-apple-ios compile target.
- Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy<T>`.
- On iOS, disable overscan compensation for external displays (removes black
bars surrounding the image).
- On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait.
- On iOS, add `set_prefers_status_bar_hidden` extension function instead of
hijacking `set_decorations` for this purpose.
- On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`.
- On iOS, add touch pressure information for touch events.
- Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms.
- On macOS, fix the signature of `-[NSView drawRect:]`.
- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application.
- On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number.
- On iOS, RedrawRequested now works for gl/metal backed views.
- On iOS, RedrawRequested is generally ordered after EventsCleared.
# 0.20.0 Alpha 2 (2019-07-09) # 0.20.0 Alpha 2 (2019-07-09)
- On X11, non-resizable windows now have maximize explicitly disabled. - On X11, non-resizable windows now have maximize explicitly disabled.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "winit" name = "winit"
version = "0.20.0-alpha2" version = "0.20.0-alpha3"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"] authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library." description = "Cross-platform window creation library."
edition = "2018" edition = "2018"
@ -25,6 +25,7 @@ libc = "0.2"
log = "0.4" log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] } serde = { version = "1", optional = true, features = ["serde_derive"] }
derivative = "1.0.2" derivative = "1.0.2"
raw-window-handle = "0.1"
[dev-dependencies] [dev-dependencies]
image = "0.21" image = "0.21"
@ -48,7 +49,7 @@ version = "0.1.3"
default_features = false default_features = false
features = ["display_link"] features = ["display_link"]
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
bitflags = "1" bitflags = "1"
[target.'cfg(target_os = "windows")'.dependencies.winapi] [target.'cfg(target_os = "windows")'.dependencies.winapi]
@ -80,10 +81,10 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "
calloop = "0.4.2" calloop = "0.4.2"
smithay-client-toolkit = "0.6" smithay-client-toolkit = "0.6"
x11-dl = "2.18.3" x11-dl = "2.18.3"
percent-encoding = "1.0" percent-encoding = "2.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
version = "0.8" version = "0.9"
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys" package = "web-sys"
@ -116,3 +117,4 @@ optional = true
package = "stdweb" package = "stdweb"
version = "0.4.18" version = "0.4.18"
optional = true optional = true

View file

@ -12,7 +12,6 @@ be used to create both games and applications. It supports the main graphical pl
- iOS - iOS
- Android - Android
- Web - Web
- via Emscripten
- via WASM - via WASM
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
@ -84,6 +83,9 @@ If your PR makes notable changes to Winit's features, please update this section
- **Fullscreen**: The windows created by winit can be put into fullscreen mode. - **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation. creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows, and if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. - **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent - **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
@ -100,6 +102,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window. - **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor. - **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Touch events**: Single-touch events. - **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture. - **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and - **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs. translating keypresses into UTF-8 characters, handling dead keys and IMEs.
@ -129,6 +132,21 @@ If your PR makes notable changes to Winit's features, please update this section
* GTK Theme Variant * GTK Theme Variant
* Base window size * Base window size
### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIWindow` object pointer
* Get the `UIViewController` object pointer
* Get the `UIView` object pointer
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility
* Deferrring system gestures
* Support for custom `UIView` derived class
* Getting the device idiom
* Getting the preferred video mode
## Usability ## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial) * `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
@ -143,55 +161,57 @@ Legend:
- ❓: Unknown status - ❓: Unknown status
### Windowing ### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten| |Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ | |Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ | |Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** | |Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ | |
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** | |Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**| |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**| |
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ | |Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ |
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ | |Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❓ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
### System information ### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ | |Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❓ |
### Input handling ### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| |Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ | |Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|❓ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A** | |Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|❓ |
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ | |Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|❓ |
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ | |Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❓ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❓ |
|Multitouch |❓ |❌ |✔️ |✔️ |❓ |❌ |❌ | |Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❓ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❓ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |❓ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ | | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ | |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ | | |Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ | |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ | | |Device movement events |❓ |❓ |❓ |❓ |❌ |❌ | |
### Pending API Reworks ### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms. Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ | |
|Event Loop 2.0 ([#459]) |✔️ |❌ |❌ |✔️ |❌ |❌ |❌ | |Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ |
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ | |Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
### Completed API Reworks ### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
[#165]: https://github.com/rust-windowing/winit/issues/165 [#165]: https://github.com/rust-windowing/winit/issues/165

View file

@ -7,7 +7,7 @@
```toml ```toml
[dependencies] [dependencies]
winit = "0.20.0-alpha2" winit = "0.20.0-alpha3"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@ -66,7 +66,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
### Platform-specific usage ### Platform-specific usage
#### Emscripten and WebAssembly #### WebAssembly
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to: Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:

41
examples/custom_events.rs Normal file
View 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,
});
}

View file

@ -1,56 +1,35 @@
use std::io::{self, Write}; use std::io::{stdin, stdout, Write};
use winit::{ use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, use winit::event_loop::{ControlFlow, EventLoop};
event_loop::{ControlFlow, EventLoop}, use winit::monitor::{MonitorHandle, VideoMode};
monitor::MonitorHandle, use winit::window::{Fullscreen, WindowBuilder};
window::WindowBuilder,
};
fn main() { fn main() {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
#[cfg(target_os = "macos")] print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
let mut macos_use_simple_fullscreen = false; stdout().flush().unwrap();
let monitor = { let mut num = String::new();
// On macOS there are two fullscreen modes "native" and "simple" stdin().read_line(&mut num).unwrap();
#[cfg(target_os = "macos")] let num = num.trim().parse().ok().expect("Please enter a number");
{
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
io::stdout().flush().unwrap();
let mut num = String::new(); let fullscreen = Some(match num {
io::stdin().read_line(&mut num).unwrap(); 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
let num = num.trim().parse().ok().expect("Please enter a number"); 2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
match num { _ => panic!("Please enter a valid number"),
2 => macos_use_simple_fullscreen = true, });
_ => {}
}
// Prompt for monitor when using native fullscreen
if !macos_use_simple_fullscreen {
Some(prompt_for_monitor(&event_loop))
} else {
None
}
}
#[cfg(not(target_os = "macos"))]
Some(prompt_for_monitor(&event_loop))
};
let mut is_fullscreen = monitor.is_some();
let mut is_maximized = false; let mut is_maximized = false;
let mut decorations = true; let mut decorations = true;
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_title("Hello world!") .with_title("Hello world!")
.with_fullscreen(monitor) .with_fullscreen(fullscreen.clone())
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
match event { match event {
@ -67,35 +46,14 @@ fn main() {
} => match (virtual_code, state) { } => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => { (VirtualKeyCode::F, ElementState::Pressed) => {
#[cfg(target_os = "macos")] if window.fullscreen().is_some() {
{
if macos_use_simple_fullscreen {
use winit::platform::macos::WindowExtMacOS;
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
is_fullscreen = !is_fullscreen;
}
return;
}
}
is_fullscreen = !is_fullscreen;
if !is_fullscreen {
window.set_fullscreen(None); window.set_fullscreen(None);
} else { } else {
window.set_fullscreen(Some(window.current_monitor())); window.set_fullscreen(fullscreen.clone());
} }
} }
(VirtualKeyCode::S, ElementState::Pressed) => { (VirtualKeyCode::S, ElementState::Pressed) => {
println!("window.fullscreen {:?}", window.fullscreen()); println!("window.fullscreen {:?}", window.fullscreen());
#[cfg(target_os = "macos")]
{
use winit::platform::macos::WindowExtMacOS;
println!(
"window.simple_fullscreen {:?}",
WindowExtMacOS::simple_fullscreen(&window)
);
}
} }
(VirtualKeyCode::M, ElementState::Pressed) => { (VirtualKeyCode::M, ElementState::Pressed) => {
is_maximized = !is_maximized; is_maximized = !is_maximized;
@ -121,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
} }
print!("Please write the number of the monitor to use: "); print!("Please write the number of the monitor to use: ");
io::stdout().flush().unwrap(); stdout().flush().unwrap();
let mut num = String::new(); let mut num = String::new();
io::stdin().read_line(&mut num).unwrap(); stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number"); let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = event_loop let monitor = event_loop
.available_monitors() .available_monitors()
@ -135,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
monitor monitor
} }
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
for (i, video_mode) in monitor.video_modes().enumerate() {
println!("Video mode #{}: {}", i, video_mode);
}
print!("Please write the number of the video mode to use: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let video_mode = monitor
.video_modes()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {}", video_mode);
video_mode
}

View file

@ -2,7 +2,15 @@
fn main() { fn main() {
extern crate env_logger; extern crate env_logger;
<<<<<<< HEAD
use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
=======
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, Fullscreen, WindowBuilder},
};
>>>>>>> master
use winit::{ use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
@ -21,11 +29,34 @@ fn main() {
.with_inner_size(WINDOW_SIZE.into()) .with_inner_size(WINDOW_SIZE.into())
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
let mut video_mode_id = 0usize;
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx); window_senders.insert(window.id(), tx);
thread::spawn(move || { thread::spawn(move || {
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
match event { match event {
WindowEvent::Moved { .. } => {
// We need to update our chosen video mode if the window
// was moved to an another monitor, so that the window
// appears on this monitor instead when we go fullscreen
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
video_modes = window.current_monitor().video_modes().collect();
video_mode_id = video_mode_id.min(video_modes.len());
let video_mode = video_modes.iter().nth(video_mode_id);
// Different monitors may support different video modes,
// and the index we chose previously may now point to a
// completely different video mode, so notify the user
if video_mode != previous_video_mode.as_ref() {
println!(
"Window moved to another monitor, picked video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
}
}
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: input:
KeyboardInput { KeyboardInput {
@ -46,9 +77,26 @@ fn main() {
false => CursorIcon::Default, false => CursorIcon::Default,
}), }),
D => window.set_decorations(!state), D => window.set_decorations(!state),
F => window.set_fullscreen(match state { // Cycle through video modes
true => Some(window.current_monitor()), Right | Left => {
false => None, video_mode_id = match key {
Left => video_mode_id.saturating_sub(1),
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!(
"Picking video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
}
F => window.set_fullscreen(match (state, modifiers.alt) {
(true, false) => {
Some(Fullscreen::Borderless(window.current_monitor()))
}
(true, true) => Some(Fullscreen::Exclusive(
video_modes.iter().nth(video_mode_id).unwrap().clone(),
)),
(false, _) => None,
}), }),
G => window.set_cursor_grab(state).unwrap(), G => window.set_cursor_grab(state).unwrap(),
H => window.set_cursor_visible(!state), H => window.set_cursor_visible(!state),
@ -58,6 +106,7 @@ fn main() {
println!("-> inner_position : {:?}", window.inner_position()); println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size()); println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size()); println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
} }
L => window.set_min_inner_size(match state { L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE.into()), true => Some(WINDOW_SIZE.into()),
@ -110,6 +159,7 @@ fn main() {
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: input:
KeyboardInput { KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape), virtual_keycode: Some(VirtualKeyCode::Escape),
.. ..
}, },

View file

@ -1,43 +0,0 @@
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
let event_loop: EventLoop<i32> = EventLoop::new_user_event();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let proxy = event_loop.create_proxy();
std::thread::spawn(move || {
let mut counter = 0;
// Wake up the `event_loop` once every second.
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
proxy.send_event(counter).unwrap();
counter += 1;
}
});
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
}
});
}
#[cfg(target_arch = "wasm32")]
fn main() {
panic!("Example not supported on Wasm");
}

View file

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

View file

@ -1,4 +1,17 @@
<<<<<<< HEAD
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
=======
// Limit this example to only compatible platforms.
#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
>>>>>>> master
fn main() { fn main() {
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
@ -6,6 +19,10 @@ fn main() {
platform::desktop::EventLoopExtDesktop, platform::desktop::EventLoopExtDesktop,
window::WindowBuilder, window::WindowBuilder,
}; };
<<<<<<< HEAD
=======
>>>>>>> master
let mut event_loop = EventLoop::new(); let mut event_loop = EventLoop::new();
let window = WindowBuilder::new() let window = WindowBuilder::new()
@ -40,7 +57,13 @@ fn main() {
println!("Okay we're done now for real."); println!("Okay we're done now for real.");
} }
<<<<<<< HEAD
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
fn main() { fn main() {
panic!("Example not supported on Wasm"); panic!("Example not supported on Wasm");
=======
#[cfg(any(target_os = "ios", target_os = "android"))]
fn main() {
println!("This platform doesn't support run_return.");
>>>>>>> master
} }

View file

@ -134,6 +134,10 @@ pub enum WindowEvent {
input: KeyboardInput, input: KeyboardInput,
}, },
/// Keyboard modifiers have changed
#[doc(hidden)]
ModifiersChanged { modifiers: ModifiersState },
/// The cursor has moved on the window. /// The cursor has moved on the window.
CursorMoved { CursorMoved {
device_id: DeviceId, device_id: DeviceId,
@ -304,30 +308,94 @@ pub enum TouchPhase {
Cancelled, Cancelled,
} }
/// Represents touch event /// Represents a touch event
/// ///
/// Every time user touches screen new Start event with some finger id is generated. /// Every time the user touches the screen, a new `Start` event with an unique
/// When the finger is removed from the screen End event with same id is generated. /// identifier for the finger is generated. When the finger is lifted, an `End`
/// event is generated with the same finger id.
/// ///
/// For every id there will be at least 2 events with phases Start and End (or Cancelled). /// After a `Start` event has been emitted, there may be zero or more `Move`
/// There may be 0 or more Move events. /// events when the finger is moved or the touch pressure changes.
/// ///
/// The finger id may be reused by the system after an `End` event. The user
/// should assume that a new `Start` event received with the same id has nothing
/// to do with the old finger and is a new finger.
/// ///
/// Depending on platform implementation id may or may not be reused by system after End event. /// A `Cancelled` event is emitted when the system has canceled tracking this
/// /// touch, such as when the window loses focus, or on iOS if the user moves the
/// Gesture regonizer using this event should assume that Start event received with same id /// device against their face.
/// as previously received End event is a new finger and has nothing to do with an old one.
///
/// Touch may be cancelled if for example window lost focus.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch { pub struct Touch {
pub device_id: DeviceId, pub device_id: DeviceId,
pub phase: TouchPhase, pub phase: TouchPhase,
pub location: LogicalPosition, pub location: LogicalPosition,
/// unique identifier of a finger. /// Describes how hard the screen was pressed. May be `None` if the platform
/// does not support pressure sensitivity.
///
/// ## Platform-specific
///
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64, pub id: u64,
} }
/// Describes the force of a touch event
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Force {
/// On iOS, the force is calibrated so that the same number corresponds to
/// roughly the same amount of pressure on the screen regardless of the
/// device.
Calibrated {
/// The force of the touch, where a value of 1.0 represents the force of
/// an average touch (predetermined by the system, not user-specific).
///
/// The force reported by Apple Pencil is measured along the axis of the
/// pencil. If you want a force perpendicular to the device, you need to
/// calculate this value using the `altitude_angle` value.
force: f64,
/// The maximum possible force for a touch.
///
/// The value of this field is sufficiently high to provide a wide
/// dynamic range for values of the `force` field.
max_possible_force: f64,
/// The altitude (in radians) of the stylus.
///
/// A value of 0 radians indicates that the stylus is parallel to the
/// surface. The value of this property is Pi/2 when the stylus is
/// perpendicular to the surface.
altitude_angle: Option<f64>,
},
/// If the platform reports the force as normalized, we have no way of
/// knowing how much pressure 1.0 corresponds to we know it's the maximum
/// amount of force, but as to how much force, you might either have to
/// press really really hard, or not hard at all, depending on the device.
Normalized(f64),
}
impl Force {
/// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
/// Instead of normalizing the force, you should prefer to handle
/// `Force::Calibrated` so that the amount of force the user has to apply is
/// consistent across devices.
pub fn normalized(&self) -> f64 {
match self {
Force::Calibrated {
force,
max_possible_force,
altitude_angle,
} => {
let force = match altitude_angle {
Some(altitude_angle) => force / altitude_angle.sin(),
None => *force,
};
force / max_possible_force
}
Force::Normalized(force) => *force,
}
}
}
/// Hardware-dependent keyboard scan code. /// Hardware-dependent keyboard scan code.
pub type ScanCode = u32; pub type ScanCode = u32;

View file

@ -37,9 +37,10 @@ pub struct EventLoop<T: 'static> {
/// Target that associates windows with an `EventLoop`. /// Target that associates windows with an `EventLoop`.
/// ///
/// This type exists to allow you to create new windows while Winit executes your callback. /// This type exists to allow you to create new windows while Winit executes
/// `EventLoop` will coerce into this type, so functions that take this as a parameter can also /// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
/// take `&EventLoop`. /// EventLoop<T>`), so functions that take this as a parameter can also take
/// `&EventLoop`.
pub struct EventLoopWindowTarget<T: 'static> { pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget<T>, pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
@ -99,7 +100,7 @@ impl EventLoop<()> {
/// ///
/// - **iOS:** Can only be called on the main thread. /// - **iOS:** Can only be called on the main thread.
pub fn new() -> EventLoop<()> { pub fn new() -> EventLoop<()> {
EventLoop::<()>::new_user_event() EventLoop::<()>::with_user_event()
} }
} }
@ -114,7 +115,7 @@ impl<T> EventLoop<T> {
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **iOS:** Can only be called on the main thread. /// - **iOS:** Can only be called on the main thread.
pub fn new_user_event() -> EventLoop<T> { pub fn with_user_event() -> EventLoop<T> {
EventLoop { EventLoop {
event_loop: platform_impl::EventLoop::new(), event_loop: platform_impl::EventLoop::new(),
_marker: ::std::marker::PhantomData, _marker: ::std::marker::PhantomData,
@ -172,11 +173,18 @@ impl<T> Deref for EventLoop<T> {
} }
/// Used to send custom events to `EventLoop`. /// Used to send custom events to `EventLoop`.
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> { pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>, event_loop_proxy: platform_impl::EventLoopProxy<T>,
} }
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
event_loop_proxy: self.event_loop_proxy.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> { impl<T: 'static> EventLoopProxy<T> {
/// Send an event to the `EventLoop` from which this proxy was created. This emits a /// Send an event to the `EventLoop` from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this

View file

@ -121,10 +121,9 @@ extern crate log;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
#[cfg(target_os = "windows")]
extern crate derivative; extern crate derivative;
#[macro_use] #[macro_use]
#[cfg(target_os = "windows")] #[cfg(any(target_os = "ios", target_os = "windows"))]
extern crate bitflags; extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use] #[macro_use]

View file

@ -52,17 +52,41 @@ impl Iterator for AvailableMonitorsIter {
/// - [`MonitorHandle::video_modes`][monitor_get]. /// - [`MonitorHandle::video_modes`][monitor_get].
/// ///
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes /// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Derivative)]
#[derivative(Clone, Debug = "transparent", PartialEq, Eq, Hash)]
pub struct VideoMode { pub struct VideoMode {
pub(crate) size: (u32, u32), pub(crate) video_mode: platform_impl::VideoMode,
pub(crate) bit_depth: u16, }
pub(crate) refresh_rate: u16,
impl PartialOrd for VideoMode {
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VideoMode {
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
// to `u32` there
let size: (u32, u32) = self.size().into();
let other_size: (u32, u32) = other.size().into();
self.monitor().cmp(&other.monitor()).then(
size.cmp(&other_size)
.then(
self.refresh_rate()
.cmp(&other.refresh_rate())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
)
}
} }
impl VideoMode { impl VideoMode {
/// Returns the resolution of this video mode. /// Returns the resolution of this video mode.
#[inline]
pub fn size(&self) -> PhysicalSize { pub fn size(&self) -> PhysicalSize {
self.size.into() self.video_mode.size()
} }
/// Returns the bit depth of this video mode, as in how many bits you have /// Returns the bit depth of this video mode, as in how many bits you have
@ -73,15 +97,37 @@ impl VideoMode {
/// ///
/// - **Wayland:** Always returns 32. /// - **Wayland:** Always returns 32.
/// - **iOS:** Always returns 32. /// - **iOS:** Always returns 32.
#[inline]
pub fn bit_depth(&self) -> u16 { pub fn bit_depth(&self) -> u16 {
self.bit_depth self.video_mode.bit_depth()
} }
/// Returns the refresh rate of this video mode. **Note**: the returned /// Returns the refresh rate of this video mode. **Note**: the returned
/// refresh rate is an integer approximation, and you shouldn't rely on this /// refresh rate is an integer approximation, and you shouldn't rely on this
/// value to be exact. /// value to be exact.
#[inline]
pub fn refresh_rate(&self) -> u16 { pub fn refresh_rate(&self) -> u16 {
self.refresh_rate self.video_mode.refresh_rate()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
self.video_mode.monitor()
}
}
impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} @ {} Hz ({} bpp)",
self.size().width,
self.size().height,
self.refresh_rate(),
self.bit_depth()
)
} }
} }
@ -90,7 +136,7 @@ impl VideoMode {
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
/// ///
/// [`Window`]: ../window/struct.Window.html /// [`Window`]: ../window/struct.Window.html
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MonitorHandle { pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle, pub(crate) inner: platform_impl::MonitorHandle,
} }

View file

@ -4,13 +4,13 @@ use std::os::raw::c_void;
use crate::{ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
/// Additional methods on `EventLoop` that are specific to iOS. /// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS { pub trait EventLoopExtIOS {
/// Returns the idiom (phone/tablet/tv/etc) for the current device. /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom; fn idiom(&self) -> Idiom;
} }
@ -20,32 +20,80 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
} }
} }
/// Additional methods on `Window` that are specific to iOS. /// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS { pub trait WindowExtIOS {
/// Returns a pointer to the `UIWindow` that is used by this window. /// Returns a pointer to the [`UIWindow`] that is used by this window.
/// ///
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
fn ui_window(&self) -> *mut c_void; fn ui_window(&self) -> *mut c_void;
/// Returns a pointer to the `UIViewController` that is used by this window. /// Returns a pointer to the [`UIViewController`] that is used by this window.
/// ///
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
fn ui_view_controller(&self) -> *mut c_void; fn ui_view_controller(&self) -> *mut c_void;
/// Returns a pointer to the `UIView` that is used by this window. /// Returns a pointer to the [`UIView`] that is used by this window.
/// ///
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void; fn ui_view(&self) -> *mut c_void;
/// Sets the HiDpi factor used by this window. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
/// ///
/// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`. /// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn set_hidpi_factor(&self, hidpi_factor: f64); fn set_hidpi_factor(&self, hidpi_factor: f64);
/// Sets the valid orientations for screens showing this `Window`. /// Sets the valid orientations for the [`Window`].
/// ///
/// On iPhones and iPods upside down portrait is never enabled. /// The default value is [`ValidOrientations::LandscapeAndPortrait`].
///
/// This changes the value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
/// and then calls
/// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
fn set_valid_orientations(&self, valid_orientations: ValidOrientations); fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
/// Sets whether the [`Window`] prefers the home indicator hidden.
///
/// The default is to prefer showing the home indicator.
///
/// This changes the value returned by
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn set_prefers_home_indicator_hidden(&self, hidden: bool);
/// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling.
///
/// This changes the value returned by
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
/// Sets whether the [`Window`] prefers the status bar hidden.
///
/// The default is to prefer showing the status bar.
///
/// This changes the value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
fn set_prefers_status_bar_hidden(&self, hidden: bool);
} }
impl WindowExtIOS for Window { impl WindowExtIOS for Window {
@ -73,23 +121,79 @@ impl WindowExtIOS for Window {
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations) self.window.set_valid_orientations(valid_orientations)
} }
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window.set_prefers_home_indicator_hidden(hidden)
}
#[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.window
.set_preferred_screen_edges_deferring_system_gestures(edges)
}
#[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.window.set_prefers_status_bar_hidden(hidden)
}
} }
/// Additional methods on `WindowBuilder` that are specific to iOS. /// Additional methods on [`WindowBuilder`] that are specific to iOS.
pub trait WindowBuilderExtIOS { pub trait WindowBuilderExtIOS {
/// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided. /// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
/// ///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]` /// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc).
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the `contentScaleFactor` of the underlying `UIWindow` to `hidpi_factor`. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
/// ///
/// The default value is device dependent, and it's recommended GLES or Metal applications set /// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to `MonitorHandle::hidpi_factor()`. /// this to [`MonitorHandle::hidpi_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder; fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the `Window`. /// Sets the valid orientations for the [`Window`].
///
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
///
/// This sets the initial value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder; fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
/// Sets whether the [`Window`] prefers the home indicator hidden.
///
/// The default is to prefer showing the home indicator.
///
/// This sets the initial value returned by
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
/// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling.
///
/// This sets the initial value returned by
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_preferred_screen_edges_deferring_system_gestures(
self,
edges: ScreenEdge,
) -> WindowBuilder;
/// Sets whether the [`Window`] prefers the status bar hidden.
///
/// The default is to prefer showing the status bar.
///
/// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
} }
impl WindowBuilderExtIOS for WindowBuilder { impl WindowBuilderExtIOS for WindowBuilder {
@ -110,12 +214,41 @@ impl WindowBuilderExtIOS for WindowBuilder {
self.platform_specific.valid_orientations = valid_orientations; self.platform_specific.valid_orientations = valid_orientations;
self self
} }
#[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
self.platform_specific.prefers_home_indicator_hidden = hidden;
self
}
#[inline]
fn with_preferred_screen_edges_deferring_system_gestures(
mut self,
edges: ScreenEdge,
) -> WindowBuilder {
self.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges;
self
}
#[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
self.platform_specific.prefers_status_bar_hidden = hidden;
self
}
} }
/// Additional methods on `MonitorHandle` that are specific to iOS. /// Additional methods on [`MonitorHandle`] that are specific to iOS.
pub trait MonitorHandleExtIOS { pub trait MonitorHandleExtIOS {
/// Returns a pointer to the `UIScreen` that is used by this monitor. /// Returns a pointer to the [`UIScreen`] that is used by this monitor.
///
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void; fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoMode`] for this monitor.
///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoMode;
} }
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
@ -123,9 +256,14 @@ impl MonitorHandleExtIOS for MonitorHandle {
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
self.inner.ui_screen() as _ self.inner.ui_screen() as _
} }
#[inline]
fn preferred_video_mode(&self) -> VideoMode {
self.inner.preferred_video_mode()
}
} }
/// Valid orientations for a particular `Window`. /// Valid orientations for a particular [`Window`].
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum ValidOrientations { pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone /// Excludes `PortraitUpsideDown` on iphone
@ -161,3 +299,19 @@ pub enum Idiom {
TV, TV,
CarPlay, CarPlay,
} }
bitflags! {
/// The [edges] of a screen.
///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
#[derive(Default)]
pub struct ScreenEdge: u8 {
const NONE = 0;
const TOP = 1 << 0;
const LEFT = 1 << 1;
const BOTTOM = 1 << 2;
const RIGHT = 1 << 3;
const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits
| ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits;
}
}

View file

@ -129,6 +129,7 @@ pub trait WindowBuilderExtMacOS {
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder; fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0. /// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
} }
impl WindowBuilderExtMacOS for WindowBuilder { impl WindowBuilderExtMacOS for WindowBuilder {
@ -182,6 +183,12 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into()); self.platform_specific.resize_increments = Some(increments.into());
self self
} }
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
} }
/// Additional methods on `MonitorHandle` that are specific to MacOS. /// Additional methods on `MonitorHandle` that are specific to MacOS.

View file

@ -6,14 +6,15 @@ use smithay_client_toolkit::window::{ButtonState, Theme};
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
event_loop::EventLoop, event_loop::{EventLoop, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::MonitorHandle,
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
use crate::platform_impl::{ use crate::platform_impl::{
x11::{ffi::XVisualInfo, XConnection}, x11::{ffi::XVisualInfo, XConnection},
EventLoop as LinuxEventLoop, Window as LinuxWindow, EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
}; };
// TODO: stupid hack so that glutin can do its work // TODO: stupid hack so that glutin can do its work
@ -90,6 +91,57 @@ impl Theme for WaylandThemeObject {
} }
} }
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the `EventLoopWindowTarget` uses Wayland.
fn is_wayland(&self) -> bool;
///
/// True if the `EventLoopWindowTarget` uses X11.
fn is_x11(&self) -> bool;
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// `EventLoopWindowTarget`.
///
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit `EventLoop` is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
#[inline]
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.p {
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.display().get_display_ptr() as *mut _)
}
_ => None,
}
}
}
/// Additional methods on `EventLoop` that are specific to Unix. /// Additional methods on `EventLoop` that are specific to Unix.
pub trait EventLoopExtUnix { pub trait EventLoopExtUnix {
/// Builds a new `EventLoops` that is forced to use X11. /// Builds a new `EventLoops` that is forced to use X11.
@ -101,22 +153,6 @@ pub trait EventLoopExtUnix {
fn new_wayland() -> Self fn new_wayland() -> Self
where where
Self: Sized; Self: Sized;
/// True if the `EventLoop` uses Wayland.
fn is_wayland(&self) -> bool;
/// True if the `EventLoop` uses X11.
fn is_x11(&self) -> bool;
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventLoop`.
///
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `EventLoop` is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
} }
impl<T> EventLoopExtUnix for EventLoop<T> { impl<T> EventLoopExtUnix for EventLoop<T> {
@ -138,33 +174,6 @@ impl<T> EventLoopExtUnix for EventLoop<T> {
_marker: ::std::marker::PhantomData, _marker: ::std::marker::PhantomData,
} }
} }
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
}
#[inline]
fn is_x11(&self) -> bool {
!self.event_loop.is_wayland()
}
#[inline]
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.event_loop {
LinuxEventLoop::X(ref e) => Some(e.x_connection().clone()),
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.event_loop {
LinuxEventLoop::Wayland(ref e) => Some(e.display().get_display_ptr() as *mut _),
_ => None,
}
}
} }
/// Additional methods on `Window` that are specific to Unix. /// Additional methods on `Window` that are specific to Unix.
@ -258,17 +267,17 @@ impl WindowExtUnix for Window {
} }
#[inline] #[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> { fn set_urgent(&self, is_urgent: bool) {
match self.window { if let LinuxWindow::X(ref w) = self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()), w.set_urgent(is_urgent);
_ => None,
} }
} }
#[inline] #[inline]
fn set_urgent(&self, is_urgent: bool) { fn xcb_connection(&self) -> Option<*mut raw::c_void> {
if let LinuxWindow::X(ref w) = self.window { match self.window {
w.set_urgent(is_urgent); LinuxWindow::X(ref w) => Some(w.xcb_connection()),
_ => None,
} }
} }
@ -304,82 +313,82 @@ impl WindowExtUnix for Window {
/// Additional methods on `WindowBuilder` that are specific to Unix. /// Additional methods on `WindowBuilder` that are specific to Unix.
pub trait WindowBuilderExtUnix { pub trait WindowBuilderExtUnix {
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder; fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder; fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11. /// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
fn with_class(self, class: String, instance: String) -> WindowBuilder; fn with_class(self, class: String, instance: String) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11. /// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder; fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11. /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder; fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11. /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder; fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with resize increment hint. Only implemented on X11. /// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; fn with_resize_increments(self, increments: LogicalSize) -> Self;
/// Build window with base size hint. Only implemented on X11. /// Build window with base size hint. Only implemented on X11.
fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder; fn with_base_size(self, base_size: LogicalSize) -> Self;
/// Build window with a given application ID. It should match the `.desktop` file distributed with /// Build window with a given application ID. It should match the `.desktop` file distributed with
/// your program. Only relevant on Wayland. /// your program. Only relevant on Wayland.
/// ///
/// For details about application ID conventions, see the /// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_app_id(self, app_id: String) -> WindowBuilder; fn with_app_id(self, app_id: String) -> Self;
} }
impl WindowBuilderExtUnix for WindowBuilder { impl WindowBuilderExtUnix for WindowBuilder {
#[inline] #[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> WindowBuilder { fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
self.platform_specific.visual_infos = self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) }); Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
self self
} }
#[inline] #[inline]
fn with_x11_screen(mut self, screen_id: i32) -> WindowBuilder { fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id); self.platform_specific.screen_id = Some(screen_id);
self self
} }
#[inline] #[inline]
fn with_class(mut self, instance: String, class: String) -> WindowBuilder { fn with_class(mut self, instance: String, class: String) -> Self {
self.platform_specific.class = Some((instance, class)); self.platform_specific.class = Some((instance, class));
self self
} }
#[inline] #[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder { fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect; self.platform_specific.override_redirect = override_redirect;
self self
} }
#[inline] #[inline]
fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder { fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_type = x11_window_type; self.platform_specific.x11_window_types = x11_window_types;
self self
} }
#[inline] #[inline]
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { fn with_gtk_theme_variant(mut self, variant: String) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder {
self.platform_specific.gtk_theme_variant = Some(variant); self.platform_specific.gtk_theme_variant = Some(variant);
self self
} }
#[inline] #[inline]
fn with_app_id(mut self, app_id: String) -> WindowBuilder { fn with_resize_increments(mut self, increments: LogicalSize) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size(mut self, base_size: LogicalSize) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
fn with_app_id(mut self, app_id: String) -> Self {
self.platform_specific.app_id = Some(app_id); self.platform_specific.app_id = Some(app_id);
self self
} }

View file

@ -75,6 +75,7 @@ impl EventLoop {
android_glue::MotionAction::Cancel => TouchPhase::Cancelled, android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
}, },
location, location,
force: None, // TODO
id: motion.pointer_id as u64, id: motion.pointer_id as u64,
device_id: DEVICE_ID, device_id: DEVICE_ID,
}), }),

View file

@ -1,363 +0,0 @@
#![allow(dead_code, non_camel_case_types, non_snake_case)]
#[cfg(test)]
use std::mem;
use std::os::raw::{c_char, c_double, c_int, c_long, c_ulong, c_ushort, c_void};
pub type EM_BOOL = c_int;
pub type EM_UTF8 = c_char;
pub type EMSCRIPTEN_RESULT = c_int;
pub const EM_TRUE: EM_BOOL = 1;
pub const EM_FALSE: EM_BOOL = 0;
// values for EMSCRIPTEN_RESULT
pub const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0;
pub const EMSCRIPTEN_RESULT_DEFERRED: c_int = 1;
pub const EMSCRIPTEN_RESULT_NOT_SUPPORTED: c_int = -1;
pub const EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: c_int = -2;
pub const EMSCRIPTEN_RESULT_INVALID_TARGET: c_int = -3;
pub const EMSCRIPTEN_RESULT_UNKNOWN_TARGET: c_int = -4;
pub const EMSCRIPTEN_RESULT_INVALID_PARAM: c_int = -5;
pub const EMSCRIPTEN_RESULT_FAILED: c_int = -6;
pub const EMSCRIPTEN_RESULT_NO_DATA: c_int = -7;
// values for EMSCRIPTEN EVENT
pub const EMSCRIPTEN_EVENT_KEYPRESS: c_int = 1;
pub const EMSCRIPTEN_EVENT_KEYDOWN: c_int = 2;
pub const EMSCRIPTEN_EVENT_KEYUP: c_int = 3;
pub const EMSCRIPTEN_EVENT_CLICK: c_int = 4;
pub const EMSCRIPTEN_EVENT_MOUSEDOWN: c_int = 5;
pub const EMSCRIPTEN_EVENT_MOUSEUP: c_int = 6;
pub const EMSCRIPTEN_EVENT_DBLCLICK: c_int = 7;
pub const EMSCRIPTEN_EVENT_MOUSEMOVE: c_int = 8;
pub const EMSCRIPTEN_EVENT_WHEEL: c_int = 9;
pub const EMSCRIPTEN_EVENT_RESIZE: c_int = 10;
pub const EMSCRIPTEN_EVENT_SCROLL: c_int = 11;
pub const EMSCRIPTEN_EVENT_BLUR: c_int = 12;
pub const EMSCRIPTEN_EVENT_FOCUS: c_int = 13;
pub const EMSCRIPTEN_EVENT_FOCUSIN: c_int = 14;
pub const EMSCRIPTEN_EVENT_FOCUSOUT: c_int = 15;
pub const EMSCRIPTEN_EVENT_DEVICEORIENTATION: c_int = 16;
pub const EMSCRIPTEN_EVENT_DEVICEMOTION: c_int = 17;
pub const EMSCRIPTEN_EVENT_ORIENTATIONCHANGE: c_int = 18;
pub const EMSCRIPTEN_EVENT_FULLSCREENCHANGE: c_int = 19;
pub const EMSCRIPTEN_EVENT_POINTERLOCKCHANGE: c_int = 20;
pub const EMSCRIPTEN_EVENT_VISIBILITYCHANGE: c_int = 21;
pub const EMSCRIPTEN_EVENT_TOUCHSTART: c_int = 22;
pub const EMSCRIPTEN_EVENT_TOUCHEND: c_int = 23;
pub const EMSCRIPTEN_EVENT_TOUCHMOVE: c_int = 24;
pub const EMSCRIPTEN_EVENT_TOUCHCANCEL: c_int = 25;
pub const EMSCRIPTEN_EVENT_GAMEPADCONNECTED: c_int = 26;
pub const EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: c_int = 27;
pub const EMSCRIPTEN_EVENT_BEFOREUNLOAD: c_int = 28;
pub const EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE: c_int = 29;
pub const EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE: c_int = 30;
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: c_int = 31;
pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: c_int = 32;
pub const EMSCRIPTEN_EVENT_MOUSEENTER: c_int = 33;
pub const EMSCRIPTEN_EVENT_MOUSELEAVE: c_int = 34;
pub const EMSCRIPTEN_EVENT_MOUSEOVER: c_int = 35;
pub const EMSCRIPTEN_EVENT_MOUSEOUT: c_int = 36;
pub const EMSCRIPTEN_EVENT_CANVASRESIZED: c_int = 37;
pub const EMSCRIPTEN_EVENT_POINTERLOCKERROR: c_int = 38;
pub const EM_HTML5_SHORT_STRING_LEN_BYTES: usize = 32;
pub const DOM_KEY_LOCATION_STANDARD: c_ulong = 0x00;
pub const DOM_KEY_LOCATION_LEFT: c_ulong = 0x01;
pub const DOM_KEY_LOCATION_RIGHT: c_ulong = 0x02;
pub const DOM_KEY_LOCATION_NUMPAD: c_ulong = 0x03;
pub type em_callback_func = Option<unsafe extern "C" fn()>;
pub type em_key_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
keyEvent: *const EmscriptenKeyboardEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_mouse_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
mouseEvent: *const EmscriptenMouseEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_pointerlockchange_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
pointerlockChangeEvent: *const EmscriptenPointerlockChangeEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_fullscreenchange_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
fullscreenChangeEvent: *const EmscriptenFullscreenChangeEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
pub type em_touch_callback_func = Option<
unsafe extern "C" fn(
eventType: c_int,
touchEvent: *const EmscriptenTouchEvent,
userData: *mut c_void,
) -> EM_BOOL,
>;
#[repr(C)]
pub struct EmscriptenFullscreenChangeEvent {
pub isFullscreen: c_int,
pub fullscreenEnabled: c_int,
pub nodeName: [c_char; 128usize],
pub id: [c_char; 128usize],
pub elementWidth: c_int,
pub elementHeight: c_int,
pub screenWidth: c_int,
pub screenHeight: c_int,
}
#[test]
fn bindgen_test_layout_EmscriptenFullscreenChangeEvent() {
assert_eq!(mem::size_of::<EmscriptenFullscreenChangeEvent>(), 280usize);
assert_eq!(mem::align_of::<EmscriptenFullscreenChangeEvent>(), 4usize);
}
#[repr(C)]
#[derive(Debug, Copy)]
pub struct EmscriptenKeyboardEvent {
pub key: [c_char; 32usize],
pub code: [c_char; 32usize],
pub location: c_ulong,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub repeat: c_int,
pub locale: [c_char; 32usize],
pub charValue: [c_char; 32usize],
pub charCode: c_ulong,
pub keyCode: c_ulong,
pub which: c_ulong,
}
#[test]
fn bindgen_test_layout_EmscriptenKeyboardEvent() {
assert_eq!(mem::size_of::<EmscriptenKeyboardEvent>(), 184usize);
assert_eq!(mem::align_of::<EmscriptenKeyboardEvent>(), 8usize);
}
impl Clone for EmscriptenKeyboardEvent {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenMouseEvent {
pub timestamp: f64,
pub screenX: c_long,
pub screenY: c_long,
pub clientX: c_long,
pub clientY: c_long,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub button: c_ushort,
pub buttons: c_ushort,
pub movementX: c_long,
pub movementY: c_long,
pub targetX: c_long,
pub targetY: c_long,
pub canvasX: c_long,
pub canvasY: c_long,
pub padding: c_long,
}
#[test]
fn bindgen_test_layout_EmscriptenMouseEvent() {
assert_eq!(mem::size_of::<EmscriptenMouseEvent>(), 120usize);
assert_eq!(mem::align_of::<EmscriptenMouseEvent>(), 8usize);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenTouchPoint {
pub identifier: c_long,
pub screenX: c_long,
pub screenY: c_long,
pub clientX: c_long,
pub clientY: c_long,
pub pageX: c_long,
pub pageY: c_long,
pub isChanged: c_int,
pub onTarget: c_int,
pub targetX: c_long,
pub targetY: c_long,
pub canvasX: c_long,
pub canvasY: c_long,
}
#[test]
fn bindgen_test_layout_EmscriptenTouchPoint() {
assert_eq!(mem::size_of::<EmscriptenTouchPoint>(), 96usize);
assert_eq!(mem::align_of::<EmscriptenTouchPoint>(), 8usize);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct EmscriptenTouchEvent {
pub numTouches: c_int,
pub ctrlKey: c_int,
pub shiftKey: c_int,
pub altKey: c_int,
pub metaKey: c_int,
pub touches: [EmscriptenTouchPoint; 32usize],
}
#[test]
fn bindgen_test_layout_EmscriptenTouchEvent() {
assert_eq!(mem::size_of::<EmscriptenTouchEvent>(), 3096usize);
assert_eq!(mem::align_of::<EmscriptenTouchEvent>(), 8usize);
}
#[repr(C)]
pub struct EmscriptenPointerlockChangeEvent {
pub isActive: c_int,
pub nodeName: [c_char; 128usize],
pub id: [c_char; 128usize],
}
#[test]
fn bindgen_test_layout_EmscriptenPointerlockChangeEvent() {
assert_eq!(mem::size_of::<EmscriptenPointerlockChangeEvent>(), 260usize);
assert_eq!(mem::align_of::<EmscriptenPointerlockChangeEvent>(), 4usize);
}
extern "C" {
pub fn emscripten_set_canvas_size(width: c_int, height: c_int) -> EMSCRIPTEN_RESULT;
pub fn emscripten_get_canvas_size(
width: *mut c_int,
height: *mut c_int,
is_fullscreen: *mut c_int,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_element_css_size(
target: *const c_char,
width: c_double,
height: c_double,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_get_element_css_size(
target: *const c_char,
width: *mut c_double,
height: *mut c_double,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_request_pointerlock(
target: *const c_char,
deferUntilInEventHandler: EM_BOOL,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_exit_pointerlock() -> EMSCRIPTEN_RESULT;
pub fn emscripten_request_fullscreen(
target: *const c_char,
deferUntilInEventHandler: EM_BOOL,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_exit_fullscreen() -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_keydown_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_key_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_keyup_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_key_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mousemove_callback(
target: *const c_char,
user_data: *mut c_void,
use_capture: EM_BOOL,
callback: em_mouse_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mousedown_callback(
target: *const c_char,
user_data: *mut c_void,
use_capture: EM_BOOL,
callback: em_mouse_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_mouseup_callback(
target: *const c_char,
user_data: *mut c_void,
use_capture: EM_BOOL,
callback: em_mouse_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_hide_mouse();
pub fn emscripten_get_device_pixel_ratio() -> f64;
pub fn emscripten_set_pointerlockchange_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_pointerlockchange_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_fullscreenchange_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: EM_BOOL,
callback: em_fullscreenchange_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_asm_const(code: *const c_char);
pub fn emscripten_set_main_loop(
func: em_callback_func,
fps: c_int,
simulate_infinite_loop: EM_BOOL,
);
pub fn emscripten_cancel_main_loop();
pub fn emscripten_set_touchstart_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchend_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchmove_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
pub fn emscripten_set_touchcancel_callback(
target: *const c_char,
userData: *mut c_void,
useCapture: c_int,
callback: em_touch_callback_func,
) -> EMSCRIPTEN_RESULT;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -16,15 +16,14 @@ use crate::{
}; };
use crate::platform_impl::platform::{ use crate::platform_impl::platform::{
app_state::AppState, app_state,
ffi::{ ffi::{
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease, kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSOperatingSystemVersion, NSString, CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom,
UIApplicationMain, UIUserInterfaceIdiom,
}, },
monitor, view, MonitorHandle, monitor, view, MonitorHandle,
}; };
@ -32,13 +31,6 @@ use crate::platform_impl::platform::{
pub struct EventLoopWindowTarget<T: 'static> { pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>, receiver: Receiver<T>,
sender_to_clone: Sender<T>, sender_to_clone: Sender<T>,
capabilities: Capabilities,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
@ -64,18 +56,11 @@ impl<T: 'static> EventLoop<T> {
// this line sets up the main run loop before `UIApplicationMain` // this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers(); setup_control_flow_observers();
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
msg_send![process_info, operatingSystemVersion]
};
let capabilities = version.into();
EventLoop { EventLoop {
window_target: RootEventLoopWindowTarget { window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget { p: EventLoopWindowTarget {
receiver, receiver,
sender_to_clone, sender_to_clone,
capabilities,
}, },
_marker: PhantomData, _marker: PhantomData,
}, },
@ -95,7 +80,7 @@ impl<T: 'static> EventLoop<T> {
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS" Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
); );
AppState::will_launch(Box::new(EventLoopHandler { app_state::will_launch(Box::new(EventLoopHandler {
f: event_handler, f: event_handler,
event_loop: self.window_target, event_loop: self.window_target,
})); }));
@ -142,8 +127,7 @@ pub struct EventLoopProxy<T> {
source: CFRunLoopSourceRef, source: CFRunLoopSourceRef,
} }
unsafe impl<T> Send for EventLoopProxy<T> {} unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T> Sync for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> { impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> { fn clone(&self) -> EventLoopProxy<T> {
@ -163,7 +147,7 @@ impl<T> Drop for EventLoopProxy<T> {
impl<T> EventLoopProxy<T> { impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> { fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe { unsafe {
// just wakeup the eventloop // just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and // adding a Source to the main CFRunLoop lets us wake it up and
@ -171,7 +155,7 @@ impl<T> EventLoopProxy<T> {
let rl = CFRunLoopGetMain(); let rl = CFRunLoopGetMain();
// we want all the members of context to be zero/null, except one // we want all the members of context to be zero/null, except one
let mut context: CFRunLoopSourceContext = mem::zeroed(); let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = event_loop_proxy_handler; context.perform = Some(event_loop_proxy_handler);
let source = let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context); CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
@ -204,15 +188,40 @@ fn setup_control_flow_observers() {
unsafe { unsafe {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(), kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(), _ => unreachable!(),
} }
} }
} }
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
//
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C" fn control_flow_main_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
// end is queued with the lowest priority to ensure it is processed after other observers // end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopDestroyed will get sent after EventsCleared
extern "C" fn control_flow_end_handler( extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef, _: CFRunLoopObserverRef,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
@ -221,7 +230,7 @@ fn setup_control_flow_observers() {
unsafe { unsafe {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(), kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(), _ => unreachable!(),
} }
@ -229,6 +238,7 @@ fn setup_control_flow_observers() {
} }
let main_loop = CFRunLoopGetMain(); let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate( let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(), ptr::null_mut(),
kCFRunLoopEntry | kCFRunLoopAfterWaiting, kCFRunLoopEntry | kCFRunLoopAfterWaiting,
@ -238,6 +248,17 @@ fn setup_control_flow_observers() {
ptr::null_mut(), ptr::null_mut(),
); );
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
0, // see comment on `control_flow_main_end_handler`
control_flow_main_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate( let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(), ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting, kCFRunLoopExit | kCFRunLoopBeforeWaiting,
@ -297,20 +318,3 @@ pub unsafe fn get_idiom() -> Idiom {
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom]; let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
raw_idiom.into() raw_idiom.into()
} }
pub struct Capabilities {
pub supports_safe_area: bool,
}
impl From<NSOperatingSystemVersion> for Capabilities {
fn from(os_version: NSOperatingSystemVersion) -> Capabilities {
assert!(
os_version.major >= 8,
"`winit` current requires iOS version 8 or greater"
);
let supports_safe_area = os_version.major >= 11;
Capabilities { supports_safe_area }
}
}

View file

@ -1,10 +1,10 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::{ffi::CString, ops::BitOr, os::raw::*}; use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
use objc::{runtime::Object, Encode, Encoding}; use objc::{runtime::Object, Encode, Encoding};
use crate::platform::ios::{Idiom, ValidOrientations}; use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
pub type id = *mut Object; pub type id = *mut Object;
pub const nil: id = 0 as id; pub const nil: id = 0 as id;
@ -70,6 +70,24 @@ pub enum UITouchPhase {
Cancelled, Cancelled,
} }
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct UIEdgeInsets { pub struct UIEdgeInsets {
@ -173,6 +191,51 @@ impl UIInterfaceOrientationMask {
} }
} }
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
unsafe impl Encode for UIRectEdge {
fn encode() -> Encoding {
NSUInteger::encode()
}
}
impl From<ScreenEdge> for UIRectEdge {
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
assert_eq!(
screen_edge.bits() & !ScreenEdge::ALL.bits(),
0,
"invalid `ScreenEdge`"
);
UIRectEdge(screen_edge.bits().into())
}
}
impl Into<ScreenEdge> for UIRectEdge {
fn into(self) -> ScreenEdge {
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
fn encode() -> Encoding {
NSInteger::encode()
}
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
}
#[link(name = "UIKit", kind = "framework")] #[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")]
extern "C" { extern "C" {
@ -268,14 +331,14 @@ pub enum CFRunLoopTimerContext {}
pub struct CFRunLoopSourceContext { pub struct CFRunLoopSourceContext {
pub version: CFIndex, pub version: CFIndex,
pub info: *mut c_void, pub info: *mut c_void,
pub retain: extern "C" fn(*const c_void) -> *const c_void, pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
pub release: extern "C" fn(*const c_void), pub release: Option<extern "C" fn(*const c_void)>,
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
pub hash: extern "C" fn(*const c_void) -> CFHashCode, pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub perform: extern "C" fn(*mut c_void), pub perform: Option<extern "C" fn(*mut c_void)>,
} }
pub trait NSString: Sized { pub trait NSString: Sized {

View file

@ -79,7 +79,7 @@ use std::fmt;
pub use self::{ pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
}; };

View file

@ -1,18 +1,98 @@
use std::{ use std::{
collections::{HashSet, VecDeque}, collections::{BTreeSet, VecDeque},
fmt, fmt,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{
app_state,
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
},
}; };
use crate::platform_impl::platform::ffi::{ #[derive(Debug, PartialEq, Eq, Hash)]
id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger, pub struct VideoMode {
}; pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) screen_mode: id,
pub(crate) monitor: MonitorHandle,
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate: self.refresh_rate,
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
monitor: self.monitor.clone(),
}
}
}
impl Drop for VideoMode {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
let () = msg_send![self.screen_mode, release];
}
}
}
impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let os_capabilities = app_state::os_capabilities();
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
};
let size: CGSize = msg_send![screen_mode, size];
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
screen_mode: msg_send![screen_mode, retain],
monitor: MonitorHandle::retained_new(uiscreen),
}
}
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Inner { pub struct Inner {
uiscreen: id, uiscreen: id,
} }
@ -25,6 +105,7 @@ impl Drop for Inner {
} }
} }
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle { pub struct MonitorHandle {
inner: Inner, inner: Inner,
} }
@ -106,9 +187,10 @@ impl MonitorHandle {
impl Inner { impl Inner {
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
unsafe { unsafe {
if self.uiscreen == main_uiscreen().uiscreen { let main = main_uiscreen();
if self.uiscreen == main.uiscreen {
Some("Primary".to_string()) Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen().uiscreen { } else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
Some("Mirrored".to_string()) Some("Mirrored".to_string())
} else { } else {
uiscreens() uiscreens()
@ -140,22 +222,18 @@ impl Inner {
} }
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] }; let mut modes = BTreeSet::new();
unsafe {
let available_modes: id = msg_send![self.uiscreen, availableModes];
let available_mode_count: NSUInteger = msg_send![available_modes, count];
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] }; for i in 0..available_mode_count {
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] }; let mode: id = msg_send![available_modes, objectAtIndex: i];
modes.insert(RootVideoMode {
let mut modes = HashSet::with_capacity(available_mode_count); video_mode: VideoMode::retained_new(self.uiscreen, mode),
});
for i in 0..available_mode_count { }
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
let size: CGSize = unsafe { msg_send![mode, size] };
modes.insert(VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
});
} }
modes.into_iter() modes.into_iter()
@ -167,6 +245,15 @@ impl Inner {
pub fn ui_screen(&self) -> id { pub fn ui_screen(&self) -> id {
self.uiscreen self.uiscreen
} }
pub fn preferred_video_mode(&self) -> RootVideoMode {
unsafe {
let mode: id = msg_send![self.uiscreen, preferredMode];
RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
}
}
}
} }
// requires being run on main thread // requires being run on main thread
@ -176,8 +263,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle {
} }
// requires being run on main thread // requires being run on main thread
unsafe fn mirrored_uiscreen() -> MonitorHandle { unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen]; let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
MonitorHandle::retained_new(uiscreen) MonitorHandle::retained_new(uiscreen)
} }

View file

@ -6,18 +6,78 @@ use objc::{
}; };
use crate::{ use crate::{
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent}, event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS, platform::ios::MonitorHandleExtIOS,
window::{WindowAttributes, WindowId as RootWindowId}, platform_impl::platform::{
app_state::{self, OSCapabilities},
event_loop,
ffi::{
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
UIRectEdge, UITouchPhase, UITouchType,
},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId,
},
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
}; };
use crate::platform_impl::platform::{ macro_rules! add_property {
app_state::AppState, (
event_loop, $decl:ident,
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, $name:ident: $t:ty,
window::PlatformSpecificWindowBuilderAttributes, $setter_name:ident: |$object:ident| $after_set:expr,
DeviceId, $getter_name:ident,
}; ) => {
add_property!(
$decl,
$name: $t,
$setter_name: true, |_, _|{}; |$object| $after_set,
$getter_name,
)
};
(
$decl:ident,
$name:ident: $t:ty,
$setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr,
$getter_name:ident,
) => {
{
const VAR_NAME: &'static str = concat!("_", stringify!($name));
$decl.add_ivar::<$t>(VAR_NAME);
let setter = if $capability {
#[allow(non_snake_case)]
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
unsafe {
$object.set_ivar::<$t>(VAR_NAME, value);
}
$after_set
}
$setter_name
} else {
#[allow(non_snake_case)]
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
unsafe {
$object.set_ivar::<$t>(VAR_NAME, value);
}
$err(&app_state::os_capabilities(), "ignoring")
}
$setter_name
};
#[allow(non_snake_case)]
extern "C" fn $getter_name($object: &Object, _: Sel) -> $t {
unsafe { *$object.get_ivar::<$t>(VAR_NAME) }
}
$decl.add_method(
sel!($setter_name:),
setter as extern "C" fn(&mut Object, Sel, $t),
);
$decl.add_method(
sel!($getter_name),
$getter_name as extern "C" fn(&Object, Sel) -> $t,
);
}
};
}
// requires main thread // requires main thread
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
@ -41,7 +101,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) { extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
unsafe { unsafe {
let window: id = msg_send![object, window]; let window: id = msg_send![object, window];
AppState::handle_nonuser_event(Event::WindowEvent { assert!(!window.is_null());
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()), window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested, event: WindowEvent::RedrawRequested,
}); });
@ -52,22 +113,143 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
extern "C" fn layout_subviews(object: &Object, _: Sel) { extern "C" fn layout_subviews(object: &Object, _: Sel) {
unsafe { unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), layoutSubviews];
let window: id = msg_send![object, window]; let window: id = msg_send![object, window];
assert!(!window.is_null());
let bounds: CGRect = msg_send![window, bounds]; let bounds: CGRect = msg_send![window, bounds];
let screen: id = msg_send![window, screen]; let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace]; let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect = let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize { let size = crate::dpi::LogicalSize {
width: screen_frame.size.width, width: screen_frame.size.width as _,
height: screen_frame.size.height, height: screen_frame.size.height as _,
}; };
AppState::handle_nonuser_event(Event::WindowEvent { app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()), window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size), event: WindowEvent::Resized(size),
}); });
}
}
extern "C" fn set_content_scale_factor(
object: &mut Object,
_: Sel,
untrusted_hidpi_factor: CGFloat,
) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass]; let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), layoutSubviews]; let () = msg_send![
super(object, superclass),
setContentScaleFactor: untrusted_hidpi_factor
];
let window: id = msg_send![object, window];
// `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow
// makeKeyAndVisible]` at window creation time (either manually or internally by
// UIKit when the `UIView` is first created), in which case we send no events here
if window.is_null() {
return;
}
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
// content scale factor to a device-specific default value", so we can't use the
// parameter here. We can query the actual factor using the getter
let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor];
assert!(
!hidpi_factor.is_nan()
&& hidpi_factor.is_finite()
&& hidpi_factor.is_sign_positive()
&& hidpi_factor > 0.0,
"invalid hidpi_factor set on UIView",
);
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
}
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
unsafe {
let window: id = msg_send![object, window];
assert!(!window.is_null());
let uiscreen: id = msg_send![window, screen];
let touches_enum: id = msg_send![touches, objectEnumerator];
let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch;
loop {
let touch: id = msg_send![touches_enum, nextObject];
if touch == nil {
break;
}
let location: CGPoint = msg_send![touch, locationInView: nil];
let touch_type: UITouchType = msg_send![touch, type];
let force = if os_supports_force {
let trait_collection: id = msg_send![object, traitCollection];
let touch_capability: UIForceTouchCapability =
msg_send![trait_collection, forceTouchCapability];
// Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available {
let force: CGFloat = msg_send![touch, force];
let max_possible_force: CGFloat =
msg_send![touch, maximumPossibleForce];
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
let angle: CGFloat = msg_send![touch, altitudeAngle];
Some(angle as _)
} else {
None
};
Some(Force::Calibrated {
force: force as _,
max_possible_force: max_possible_force as _,
altitude_angle,
})
} else {
None
}
} else {
None
};
let touch_id = touch as u64;
let phase: UITouchPhase = msg_send![touch, phase];
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
force,
phase,
}),
});
}
app_state::handle_nonuser_events(touch_events);
} }
} }
@ -82,184 +264,9 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
sel!(layoutSubviews), sel!(layoutSubviews),
layout_subviews as extern "C" fn(&Object, Sel), layout_subviews as extern "C" fn(&Object, Sel),
); );
decl.register()
})
}
// requires main thread
unsafe fn get_view_controller_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiviewcontroller_class = class!(UIViewController);
extern "C" fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) {
unsafe {
object.set_ivar::<BOOL>("_prefers_status_bar_hidden", hidden);
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
}
extern "C" fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL {
unsafe { *object.get_ivar::<BOOL>("_prefers_status_bar_hidden") }
}
extern "C" fn set_supported_orientations(
object: &mut Object,
_: Sel,
orientations: UIInterfaceOrientationMask,
) {
unsafe {
object.set_ivar::<UIInterfaceOrientationMask>(
"_supported_orientations",
orientations,
);
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
}
extern "C" fn supported_orientations(
object: &Object,
_: Sel,
) -> UIInterfaceOrientationMask {
unsafe { *object.get_ivar::<UIInterfaceOrientationMask>("_supported_orientations") }
}
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
YES
}
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
.expect("Failed to declare class `WinitUIViewController`");
decl.add_ivar::<BOOL>("_prefers_status_bar_hidden");
decl.add_ivar::<UIInterfaceOrientationMask>("_supported_orientations");
decl.add_method( decl.add_method(
sel!(setPrefersStatusBarHidden:), sel!(setContentScaleFactor:),
set_prefers_status_bar_hidden as extern "C" fn(&mut Object, Sel, BOOL), set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
);
decl.add_method(
sel!(prefersStatusBarHidden),
prefers_status_bar_hidden as extern "C" fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(setSupportedInterfaceOrientations:),
set_supported_orientations
as extern "C" fn(&mut Object, Sel, UIInterfaceOrientationMask),
);
decl.add_method(
sel!(supportedInterfaceOrientations),
supported_orientations as extern "C" fn(&Object, Sel) -> UIInterfaceOrientationMask,
);
decl.add_method(
sel!(shouldAutorotate),
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
);
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
unsafe fn get_window_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiwindow_class = class!(UIWindow);
extern "C" fn become_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern "C" fn resign_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
unsafe {
let uiscreen = msg_send![object, screen];
let touches_enum: id = msg_send![touches, objectEnumerator];
let mut touch_events = Vec::new();
loop {
let touch: id = msg_send![touches_enum, nextObject];
if touch == nil {
break;
}
let location: CGPoint = msg_send![touch, locationInView: nil];
let touch_id = touch as u64;
let phase: UITouchPhase = msg_send![touch, phase];
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
phase,
}),
});
}
AppState::handle_nonuser_events(touch_events);
}
}
extern "C" fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) {
unsafe {
let () = msg_send![
super(object, class!(UIWindow)),
setContentScaleFactor: hidpi_factor
];
let view_controller: id = msg_send![object, rootViewController];
let view: id = msg_send![view_controller, view];
let () = msg_send![view, setContentScaleFactor: hidpi_factor];
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![object, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
};
AppState::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Resized(size),
})),
);
}
}
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
.expect("Failed to declare class `WinitUIWindow`");
decl.add_method(
sel!(becomeKeyWindow),
become_key_window as extern "C" fn(&Object, Sel),
);
decl.add_method(
sel!(resignKeyWindow),
resign_key_window as extern "C" fn(&Object, Sel),
); );
decl.add_method( decl.add_method(
@ -279,9 +286,114 @@ unsafe fn get_window_class() -> &'static Class {
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
); );
decl.register()
})
}
// requires main thread
unsafe fn get_view_controller_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let os_capabilities = app_state::os_capabilities();
let uiviewcontroller_class = class!(UIViewController);
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
YES
}
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
.expect("Failed to declare class `WinitUIViewController`");
decl.add_method( decl.add_method(
sel!(setContentScaleFactor:), sel!(shouldAutorotate),
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat), should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
);
add_property! {
decl,
prefers_status_bar_hidden: BOOL,
setPrefersStatusBarHidden: |object| {
unsafe {
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
},
prefersStatusBarHidden,
}
add_property! {
decl,
prefers_home_indicator_auto_hidden: BOOL,
setPrefersHomeIndicatorAutoHidden:
os_capabilities.home_indicator_hidden,
OSCapabilities::home_indicator_hidden_err_msg;
|object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
}
},
prefersHomeIndicatorAutoHidden,
}
add_property! {
decl,
supported_orientations: UIInterfaceOrientationMask,
setSupportedInterfaceOrientations: |object| {
unsafe {
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
},
supportedInterfaceOrientations,
}
add_property! {
decl,
preferred_screen_edges_deferring_system_gestures: UIRectEdge,
setPreferredScreenEdgesDeferringSystemGestures:
os_capabilities.defer_system_gestures,
OSCapabilities::defer_system_gestures_err_msg;
|object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
},
preferredScreenEdgesDeferringSystemGestures,
}
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
unsafe fn get_window_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiwindow_class = class!(UIWindow);
extern "C" fn become_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern "C" fn resign_key_window(object: &Object, _: Sel) {
unsafe {
app_state::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
.expect("Failed to declare class `WinitUIWindow`");
decl.add_method(
sel!(becomeKeyWindow),
become_key_window as extern "C" fn(&Object, Sel),
);
decl.add_method(
sel!(resignKeyWindow),
resign_key_window as extern "C" fn(&Object, Sel),
); );
CLASS = Some(decl.register()); CLASS = Some(decl.register());
@ -302,13 +414,16 @@ pub unsafe fn create_view(
let view: id = msg_send![view, initWithFrame: frame]; let view: id = msg_send![view, initWithFrame: frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance"); assert!(!view.is_null(), "Failed to initialize `UIView` instance");
let () = msg_send![view, setMultipleTouchEnabled: YES]; let () = msg_send![view, setMultipleTouchEnabled: YES];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat];
}
view view
} }
// requires main thread // requires main thread
pub unsafe fn create_view_controller( pub unsafe fn create_view_controller(
window_attributes: &WindowAttributes, _window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes, platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: id, view: id,
) -> id { ) -> id {
@ -324,16 +439,24 @@ pub unsafe fn create_view_controller(
!view_controller.is_null(), !view_controller.is_null(),
"Failed to initialize `UIViewController` instance" "Failed to initialize `UIViewController` instance"
); );
let status_bar_hidden = if window_attributes.decorations { let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden {
NO
} else {
YES YES
} else {
NO
}; };
let idiom = event_loop::get_idiom(); let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom( let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
platform_attributes.valid_orientations, platform_attributes.valid_orientations,
idiom, idiom,
); );
let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden {
YES
} else {
NO
};
let edges: UIRectEdge = platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into();
let () = msg_send![ let () = msg_send![
view_controller, view_controller,
setPrefersStatusBarHidden: status_bar_hidden setPrefersStatusBarHidden: status_bar_hidden
@ -342,6 +465,14 @@ pub unsafe fn create_view_controller(
view_controller, view_controller,
setSupportedInterfaceOrientations: supported_orientations setSupportedInterfaceOrientations: supported_orientations
]; ];
let () = msg_send![
view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
let () = msg_send![
view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
let () = msg_send![view_controller, setView: view]; let () = msg_send![view_controller, setView: view];
view_controller view_controller
} }
@ -349,7 +480,7 @@ pub unsafe fn create_view_controller(
// requires main thread // requires main thread
pub unsafe fn create_window( pub unsafe fn create_window(
window_attributes: &WindowAttributes, window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes, _platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect, frame: CGRect,
view_controller: id, view_controller: id,
) -> id { ) -> id {
@ -363,11 +494,16 @@ pub unsafe fn create_window(
"Failed to initialize `UIWindow` instance" "Failed to initialize `UIWindow` instance"
); );
let () = msg_send![window, setRootViewController: view_controller]; let () = msg_send![window, setRootViewController: view_controller];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor { match window_attributes.fullscreen {
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; Some(Fullscreen::Exclusive(ref video_mode)) => {
} let uiscreen = video_mode.monitor().ui_screen() as id;
if let &Some(ref monitor) = &window_attributes.fullscreen { let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
let () = msg_send![window, setScreen:monitor.ui_screen()]; msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => {
msg_send![window, setScreen:monitor.ui_screen()]
}
None => (),
} }
window window
@ -376,17 +512,17 @@ pub unsafe fn create_window(
pub fn create_delegate_class() { pub fn create_delegate_class() {
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL { extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
unsafe { unsafe {
AppState::did_finish_launching(); app_state::did_finish_launching();
} }
YES YES
} }
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) { extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
unsafe { AppState::handle_nonuser_event(Event::Resumed) } unsafe { app_state::handle_nonuser_event(Event::Resumed) }
} }
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
unsafe { AppState::handle_nonuser_event(Event::Suspended) } unsafe { app_state::handle_nonuser_event(Event::Suspended) }
} }
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {} extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
@ -411,8 +547,8 @@ pub fn create_delegate_class() {
}); });
} }
} }
AppState::handle_nonuser_events(events); app_state::handle_nonuser_events(events);
AppState::terminated(); app_state::terminated();
} }
} }

View file

@ -1,30 +1,34 @@
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use objc::runtime::{Class, Object, NO, YES}; use objc::runtime::{Class, Object, BOOL, NO, YES};
use crate::{ use crate::{
dpi::{self, LogicalPosition, LogicalSize}, dpi::{self, LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon, icon::Icon,
monitor::MonitorHandle as RootMonitorHandle, monitor::MonitorHandle as RootMonitorHandle,
platform::ios::{MonitorHandleExtIOS, ValidOrientations}, platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state, event_loop,
event_loop, ffi::{
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask}, id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
UIRectEdge, UIScreenOverscanCompensation,
},
monitor, view, EventLoopWindowTarget, MonitorHandle, monitor, view, EventLoopWindowTarget, MonitorHandle,
}, },
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
}; };
pub struct Inner { pub struct Inner {
pub window: id, pub window: id,
pub view_controller: id, pub view_controller: id,
pub view: id, pub view: id,
supports_safe_area: bool, gl_or_metal_backed: bool,
} }
impl Drop for Inner { impl Drop for Inner {
@ -55,7 +59,19 @@ impl Inner {
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
unsafe { unsafe {
let () = msg_send![self.view, setNeedsDisplay]; if self.gl_or_metal_backed {
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided.
//
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window);
} else {
let () = msg_send![self.view, setNeedsDisplay];
}
} }
} }
@ -63,8 +79,8 @@ impl Inner {
unsafe { unsafe {
let safe_area = self.safe_area_screen_space(); let safe_area = self.safe_area_screen_space();
Ok(LogicalPosition { Ok(LogicalPosition {
x: safe_area.origin.x, x: safe_area.origin.x as _,
y: safe_area.origin.y, y: safe_area.origin.y as _,
}) })
} }
} }
@ -73,8 +89,8 @@ impl Inner {
unsafe { unsafe {
let screen_frame = self.screen_frame(); let screen_frame = self.screen_frame();
Ok(LogicalPosition { Ok(LogicalPosition {
x: screen_frame.origin.x, x: screen_frame.origin.x as _,
y: screen_frame.origin.y, y: screen_frame.origin.y as _,
}) })
} }
} }
@ -98,8 +114,8 @@ impl Inner {
unsafe { unsafe {
let safe_area = self.safe_area_screen_space(); let safe_area = self.safe_area_screen_space();
LogicalSize { LogicalSize {
width: safe_area.size.width, width: safe_area.size.width as _,
height: safe_area.size.height, height: safe_area.size.height as _,
} }
} }
} }
@ -108,8 +124,8 @@ impl Inner {
unsafe { unsafe {
let screen_frame = self.screen_frame(); let screen_frame = self.screen_frame();
LogicalSize { LogicalSize {
width: screen_frame.size.width, width: screen_frame.size.width as _,
height: screen_frame.size.height, height: screen_frame.size.height as _,
} }
} }
} }
@ -157,26 +173,41 @@ impl Inner {
warn!("`Window::set_maximized` is ignored on iOS") warn!("`Window::set_maximized` is ignored on iOS")
} }
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe { unsafe {
match monitor { let uiscreen = match monitor {
Some(monitor) => { Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = monitor.ui_screen() as id; let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
let current: id = msg_send![self.window, screen]; let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
let bounds: CGRect = msg_send![uiscreen, bounds]; uiscreen
// this is pretty slow on iOS, so avoid doing it if we can
if uiscreen != current {
let () = msg_send![self.window, setScreen: uiscreen];
}
let () = msg_send![self.window, setFrame: bounds];
} }
None => warn!("`Window::set_fullscreen(None)` ignored on iOS"), Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id,
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
};
// this is pretty slow on iOS, so avoid doing it if we can
let current: id = msg_send![self.window, screen];
if uiscreen != current {
let () = msg_send![self.window, setScreen: uiscreen];
} }
let bounds: CGRect = msg_send![uiscreen, bounds];
let () = msg_send![self.window, setFrame: bounds];
// For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on
// each side
let () = msg_send![
uiscreen,
setOverscanCompensation: UIScreenOverscanCompensation::None
];
} }
} }
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
unsafe { unsafe {
let monitor = self.current_monitor(); let monitor = self.current_monitor();
let uiscreen = monitor.inner.ui_screen(); let uiscreen = monitor.inner.ui_screen();
@ -189,21 +220,15 @@ impl Inner {
&& screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height && screen_space_bounds.size.height == screen_bounds.size.height
{ {
Some(monitor) Some(Fullscreen::Borderless(monitor))
} else { } else {
None None
} }
} }
} }
pub fn set_decorations(&self, decorations: bool) { pub fn set_decorations(&self, _decorations: bool) {
unsafe { warn!("`Window::set_decorations` is ignored on iOS")
let status_bar_hidden = if decorations { NO } else { YES };
let () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
}
} }
pub fn set_always_on_top(&self, _always_on_top: bool) { pub fn set_always_on_top(&self, _always_on_top: bool) {
@ -238,6 +263,16 @@ impl Inner {
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
self.window.into() self.window.into()
} }
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = IOSHandle {
ui_window: self.window as _,
ui_view: self.view as _,
ui_view_controller: self.view_controller as _,
..IOSHandle::empty()
};
RawWindowHandle::IOS(handle)
}
} }
pub struct Window { pub struct Window {
@ -277,7 +312,7 @@ impl DerefMut for Window {
impl Window { impl Window {
pub fn new<T>( pub fn new<T>(
event_loop: &EventLoopWindowTarget<T>, _event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes, window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> { ) -> Result<Window, RootOsError> {
@ -293,25 +328,38 @@ impl Window {
// TODO: transparency, visible // TODO: transparency, visible
unsafe { unsafe {
let screen = window_attributes let screen = match window_attributes.fullscreen {
.fullscreen Some(Fullscreen::Exclusive(ref video_mode)) => {
.as_ref() video_mode.video_mode.monitor.ui_screen() as id
.map(|screen| screen.ui_screen() as _) }
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen()); Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
None => monitor::main_uiscreen().ui_screen(),
};
let screen_bounds: CGRect = msg_send![screen, bounds]; let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size { let frame = match window_attributes.inner_size {
Some(dim) => CGRect { Some(dim) => CGRect {
origin: screen_bounds.origin, origin: screen_bounds.origin,
size: CGSize { size: CGSize {
width: dim.width, width: dim.width as _,
height: dim.height, height: dim.height as _,
}, },
}, },
None => screen_bounds, None => screen_bounds,
}; };
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone()); let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
let gl_or_metal_backed = {
let view_class: id = msg_send![view, class];
let layer_class: id = msg_send![view_class, layerClass];
let is_metal: BOOL =
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal == YES || is_gl == YES
};
let view_controller = let view_controller =
view::create_view_controller(&window_attributes, &platform_attributes, view); view::create_view_controller(&window_attributes, &platform_attributes, view);
let window = view::create_window( let window = view::create_window(
@ -321,17 +369,41 @@ impl Window {
view_controller, view_controller,
); );
let supports_safe_area = event_loop.capabilities().supports_safe_area;
let result = Window { let result = Window {
inner: Inner { inner: Inner {
window, window,
view_controller, view_controller,
view, view,
supports_safe_area, gl_or_metal_backed,
}, },
}; };
AppState::set_key_window(window); app_state::set_key_window(window);
// Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let hidpi_factor: CGFloat = msg_send![view, contentScaleFactor];
if hidpi_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![view, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
})
.chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
})),
);
}
Ok(result) Ok(result)
} }
} }
@ -370,6 +442,36 @@ impl Inner {
msg_send![ msg_send![
self.view_controller, self.view_controller,
setSupportedInterfaceOrientations: supported_orientations setSupportedInterfaceOrientations: supported_orientations
]
}
}
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
unsafe {
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
let () = msg_send![
self.view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
}
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
unsafe {
let () = msg_send![
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
}
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
unsafe {
let status_bar_hidden = if hidden { YES } else { NO };
let () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
]; ];
} }
} }
@ -406,7 +508,7 @@ impl Inner {
// requires main thread // requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect { unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds: CGRect = msg_send![self.window, bounds]; let bounds: CGRect = msg_send![self.window, bounds];
if self.supports_safe_area { if app_state::os_capabilities().safe_area {
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets]; let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
let safe_bounds = CGRect { let safe_bounds = CGRect {
origin: CGPoint { origin: CGPoint {
@ -494,6 +596,9 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class, pub root_view_class: &'static Class,
pub hidpi_factor: Option<f64>, pub hidpi_factor: Option<f64>,
pub valid_orientations: ValidOrientations, pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
} }
impl Default for PlatformSpecificWindowBuilderAttributes { impl Default for PlatformSpecificWindowBuilderAttributes {
@ -502,6 +607,9 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView), root_view_class: class!(UIView),
hidpi_factor: None, hidpi_factor: None,
valid_orientations: Default::default(), valid_orientations: Default::default(),
prefers_home_indicator_hidden: false,
prefers_status_bar_hidden: false,
preferred_screen_edges_deferring_system_gestures: Default::default(),
} }
} }
} }

View file

@ -1,15 +0,0 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
#![allow(dead_code)]
use std::os::raw::{c_char, c_int, c_void};
pub const RTLD_LAZY: c_int = 0x001;
pub const RTLD_NOW: c_int = 0x002;
#[link(name = "dl")]
extern "C" {
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
pub fn dlerror() -> *mut c_char;
pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
pub fn dlclose(handle: *mut c_void) -> c_int;
}

View file

@ -1,23 +1,25 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] #![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem, os::raw::*, sync::Arc}; use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc};
use parking_lot::Mutex; use parking_lot::Mutex;
use raw_window_handle::RawWindowHandle;
use smithay_client_toolkit::reexports::client::ConnectError; use smithay_client_toolkit::reexports::client::ConnectError;
pub use self::x11::XNotSupported; pub use self::x11::XNotSupported;
use self::x11::{ffi::XVisualInfo, XConnection, XError}; use self::x11::{
ffi::XVisualInfo, get_xtarget, util::WindowType as XWindowType, XConnection, XError,
};
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::Event, event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon, icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
}; };
mod dlopen;
pub mod wayland; pub mod wayland;
pub mod x11; pub mod x11;
@ -30,7 +32,7 @@ pub mod x11;
/// If this variable is set with any other value, winit will panic. /// If this variable is set with any other value, winit will panic.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Clone, Default)] #[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
pub visual_infos: Option<XVisualInfo>, pub visual_infos: Option<XVisualInfo>,
pub screen_id: Option<i32>, pub screen_id: Option<i32>,
@ -38,11 +40,27 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub base_size: Option<(u32, u32)>, pub base_size: Option<(u32, u32)>,
pub class: Option<(String, String)>, pub class: Option<(String, String)>,
pub override_redirect: bool, pub override_redirect: bool,
pub x11_window_type: x11::util::WindowType, pub x11_window_types: Vec<XWindowType>,
pub gtk_theme_variant: Option<String>, pub gtk_theme_variant: Option<String>,
pub app_id: Option<String>, pub app_id: Option<String>,
} }
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
visual_infos: None,
screen_id: None,
resize_increments: None,
base_size: None,
class: None,
override_redirect: false,
x11_window_types: vec![XWindowType::Normal],
gtk_theme_variant: None,
app_id: None,
}
}
}
lazy_static! { lazy_static! {
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) }; { Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
@ -92,7 +110,7 @@ impl DeviceId {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle { pub enum MonitorHandle {
X(x11::MonitorHandle), X(x11::MonitorHandle),
Wayland(wayland::MonitorHandle), Wayland(wayland::MonitorHandle),
@ -140,7 +158,7 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> { pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
match self { match self {
MonitorHandle::X(m) => Box::new(m.video_modes()), MonitorHandle::X(m) => Box::new(m.video_modes()),
MonitorHandle::Wayland(m) => Box::new(m.video_modes()), MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
@ -148,6 +166,46 @@ impl MonitorHandle {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoMode {
X(x11::VideoMode),
Wayland(wayland::VideoMode),
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
match self {
&VideoMode::X(ref m) => m.size(),
&VideoMode::Wayland(ref m) => m.size(),
}
}
#[inline]
pub fn bit_depth(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.bit_depth(),
&VideoMode::Wayland(ref m) => m.bit_depth(),
}
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.refresh_rate(),
&VideoMode::Wayland(ref m) => m.refresh_rate(),
}
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
match self {
&VideoMode::X(ref m) => m.monitor(),
&VideoMode::Wayland(ref m) => m.monitor(),
}
}
}
impl Window { impl Window {
#[inline] #[inline]
pub fn new<T>( pub fn new<T>(
@ -310,17 +368,15 @@ impl Window {
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
match self { match self {
&Window::X(ref w) => w.fullscreen(), &Window::X(ref w) => w.fullscreen(),
&Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle { &Window::Wayland(ref w) => w.fullscreen(),
inner: MonitorHandle::Wayland(monitor_id),
}),
} }
} }
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
match self { match self {
&Window::X(ref w) => w.set_fullscreen(monitor), &Window::X(ref w) => w.set_fullscreen(monitor),
&Window::Wayland(ref w) => w.set_fullscreen(monitor), &Window::Wayland(ref w) => w.set_fullscreen(monitor),
@ -402,6 +458,13 @@ impl Window {
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()), &Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
} }
} }
pub fn raw_window_handle(&self) -> RawWindowHandle {
match self {
&Window::X(ref window) => RawWindowHandle::X11(window.raw_window_handle()),
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
}
}
} }
unsafe extern "C" fn x_error_callback( unsafe extern "C" fn x_error_callback(
@ -410,14 +473,16 @@ unsafe extern "C" fn x_error_callback(
) -> c_int { ) -> c_int {
let xconn_lock = X11_BACKEND.lock(); let xconn_lock = X11_BACKEND.lock();
if let Ok(ref xconn) = *xconn_lock { if let Ok(ref xconn) = *xconn_lock {
let mut buf: [c_char; 1024] = mem::uninitialized(); // `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
(xconn.xlib.XGetErrorText)( (xconn.xlib.XGetErrorText)(
display, display,
(*event).error_code as c_int, (*event).error_code as c_int,
buf.as_mut_ptr(), buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int, buf.len() as c_int,
); );
let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy(); let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
let error = XError { let error = XError {
description: description.into_owned(), description: description.into_owned(),
@ -439,12 +504,20 @@ pub enum EventLoop<T: 'static> {
X(x11::EventLoop<T>), X(x11::EventLoop<T>),
} }
#[derive(Clone)]
pub enum EventLoopProxy<T: 'static> { pub enum EventLoopProxy<T: 'static> {
X(x11::EventLoopProxy<T>), X(x11::EventLoopProxy<T>),
Wayland(wayland::EventLoopProxy<T>), Wayland(wayland::EventLoopProxy<T>),
} }
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
match self {
EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()),
EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()),
}
}
}
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> { pub fn new() -> EventLoop<T> {
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) { if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
@ -502,7 +575,7 @@ impl<T: 'static> EventLoop<T> {
.into_iter() .into_iter()
.map(MonitorHandle::Wayland) .map(MonitorHandle::Wayland)
.collect(), .collect(),
EventLoop::X(ref evlp) => evlp EventLoop::X(ref evlp) => get_xtarget(&evlp.target)
.x_connection() .x_connection()
.available_monitors() .available_monitors()
.into_iter() .into_iter()
@ -515,7 +588,9 @@ impl<T: 'static> EventLoop<T> {
pub fn primary_monitor(&self) -> MonitorHandle { pub fn primary_monitor(&self) -> MonitorHandle {
match *self { match *self {
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()), EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()), EventLoop::X(ref evlp) => {
MonitorHandle::X(get_xtarget(&evlp.target).x_connection().primary_monitor())
}
} }
} }
@ -546,14 +621,6 @@ impl<T: 'static> EventLoop<T> {
} }
} }
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
EventLoop::Wayland(_) => true,
EventLoop::X(_) => false,
}
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> { pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
match *self { match *self {
EventLoop::Wayland(ref evl) => evl.window_target(), EventLoop::Wayland(ref evl) => evl.window_target(),
@ -576,6 +643,16 @@ pub enum EventLoopWindowTarget<T> {
X(x11::EventLoopWindowTarget<T>), X(x11::EventLoopWindowTarget<T>),
} }
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
EventLoopWindowTarget::Wayland(_) => true,
EventLoopWindowTarget::X(_) => false,
}
}
}
fn sticky_exit_callback<T, F>( fn sticky_exit_callback<T, F>(
evt: Event<T>, evt: Event<T>,
target: &RootELW<T>, target: &RootELW<T>,

View file

@ -16,8 +16,11 @@ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
event::ModifiersState, event::ModifiersState,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::sticky_exit_callback, platform_impl::platform::{
sticky_exit_callback, MonitorHandle as PlatformMonitorHandle,
VideoMode as PlatformVideoMode,
},
}; };
use super::{window::WindowStore, DeviceId, WindowId}; use super::{window::WindowStore, DeviceId, WindowId};
@ -87,7 +90,6 @@ pub struct EventLoop<T: 'static> {
// A handle that can be sent across threads and used to wake up the `EventLoop`. // A handle that can be sent across threads and used to wake up the `EventLoop`.
// //
// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs. // We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs.
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> { pub struct EventLoopProxy<T: 'static> {
user_sender: ::calloop::channel::Sender<T>, user_sender: ::calloop::channel::Sender<T>,
} }
@ -108,6 +110,14 @@ pub struct EventLoopWindowTarget<T> {
_marker: ::std::marker::PhantomData<T>, _marker: ::std::marker::PhantomData<T>,
} }
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_sender: self.user_sender.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> { impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.user_sender.send(event).map_err(|_| EventLoopClosed) self.user_sender.send(event).map_err(|_| EventLoopClosed)
@ -138,7 +148,7 @@ impl<T: 'static> EventLoop<T> {
let mut seat_manager = SeatManager { let mut seat_manager = SeatManager {
sink: sink.clone(), sink: sink.clone(),
relative_pointer_manager_proxy: None, relative_pointer_manager_proxy: Rc::new(RefCell::new(None)),
store: store.clone(), store: store.clone(),
seats: seats.clone(), seats: seats.clone(),
kbd_sender, kbd_sender,
@ -154,13 +164,16 @@ impl<T: 'static> EventLoop<T> {
version, version,
} => { } => {
if interface == "zwp_relative_pointer_manager_v1" { if interface == "zwp_relative_pointer_manager_v1" {
seat_manager.relative_pointer_manager_proxy = Some( let relative_pointer_manager_proxy = registry
registry .bind(version, id, move |pointer_manager| {
.bind(version, id, move |pointer_manager| { pointer_manager.implement_closure(|_, _| (), ())
pointer_manager.implement_closure(|_, _| (), ()) })
}) .unwrap();
.unwrap(),
) *seat_manager
.relative_pointer_manager_proxy
.try_borrow_mut()
.unwrap() = Some(relative_pointer_manager_proxy);
} }
if interface == "wl_seat" { if interface == "wl_seat" {
seat_manager.add_seat(id, version, registry) seat_manager.add_seat(id, version, registry)
@ -304,6 +317,25 @@ impl<T: 'static> EventLoop<T> {
// send pending events to the server // send pending events to the server
self.display.flush().expect("Wayland connection lost."); self.display.flush().expect("Wayland connection lost.");
// During the run of the user callback, some other code monitoring and reading the
// wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the wayland socket, to avoid getting stuck.
let instant_wakeup = {
let window_target = match self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt,
_ => unreachable!(),
};
let dispatched = window_target
.evq
.borrow_mut()
.dispatch_pending()
.expect("Wayland connection lost.");
dispatched > 0
};
match control_flow { match control_flow {
ControlFlow::Exit => break, ControlFlow::Exit => break,
ControlFlow::Poll => { ControlFlow::Poll => {
@ -318,7 +350,12 @@ impl<T: 'static> EventLoop<T> {
); );
} }
ControlFlow::Wait => { ControlFlow::Wait => {
self.inner_loop.dispatch(None, &mut ()).unwrap(); let timeout = if instant_wakeup {
Some(::std::time::Duration::from_millis(0))
} else {
None
};
self.inner_loop.dispatch(timeout, &mut ()).unwrap();
callback( callback(
crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled { crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled {
start: Instant::now(), start: Instant::now(),
@ -331,7 +368,7 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::WaitUntil(deadline) => { ControlFlow::WaitUntil(deadline) => {
let start = Instant::now(); let start = Instant::now();
// compute the blocking duration // compute the blocking duration
let duration = if deadline > start { let duration = if deadline > start && !instant_wakeup {
deadline - start deadline - start
} else { } else {
::std::time::Duration::from_millis(0) ::std::time::Duration::from_millis(0)
@ -380,15 +417,17 @@ impl<T: 'static> EventLoop<T> {
available_monitors(&self.outputs) available_monitors(&self.outputs)
} }
pub fn display(&self) -> &Display {
&*self.display
}
pub fn window_target(&self) -> &RootELW<T> { pub fn window_target(&self) -> &RootELW<T> {
&self.window_target &self.window_target
} }
} }
impl<T> EventLoopWindowTarget<T> {
pub fn display(&self) -> &Display {
&*self.display
}
}
/* /*
* Private EventLoop Internals * Private EventLoop Internals
*/ */
@ -457,7 +496,7 @@ struct SeatManager<T: 'static> {
store: Arc<Mutex<WindowStore>>, store: Arc<Mutex<WindowStore>>,
seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>, seats: Arc<Mutex<Vec<(u32, wl_seat::WlSeat)>>>,
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>, kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>, relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
} }
impl<T: 'static> SeatManager<T> { impl<T: 'static> SeatManager<T> {
@ -469,7 +508,7 @@ impl<T: 'static> SeatManager<T> {
store: self.store.clone(), store: self.store.clone(),
pointer: None, pointer: None,
relative_pointer: None, relative_pointer: None,
relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.as_ref().cloned(), relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(),
keyboard: None, keyboard: None,
touch: None, touch: None,
kbd_sender: self.kbd_sender.clone(), kbd_sender: self.kbd_sender.clone(),
@ -501,7 +540,7 @@ struct SeatData<T> {
kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>, kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>,
pointer: Option<wl_pointer::WlPointer>, pointer: Option<wl_pointer::WlPointer>,
relative_pointer: Option<ZwpRelativePointerV1>, relative_pointer: Option<ZwpRelativePointerV1>,
relative_pointer_manager_proxy: Option<ZwpRelativePointerManagerV1>, relative_pointer_manager_proxy: Rc<RefCell<Option<ZwpRelativePointerManagerV1>>>,
keyboard: Option<wl_keyboard::WlKeyboard>, keyboard: Option<wl_keyboard::WlKeyboard>,
touch: Option<wl_touch::WlTouch>, touch: Option<wl_touch::WlTouch>,
modifiers_tracker: Arc<Mutex<ModifiersState>>, modifiers_tracker: Arc<Mutex<ModifiersState>>,
@ -521,17 +560,19 @@ impl<T: 'static> SeatData<T> {
self.modifiers_tracker.clone(), self.modifiers_tracker.clone(),
)); ));
self.relative_pointer = self.relative_pointer = self
self.relative_pointer_manager_proxy .relative_pointer_manager_proxy
.as_ref() .try_borrow()
.and_then(|manager| { .unwrap()
super::pointer::implement_relative_pointer( .as_ref()
self.sink.clone(), .and_then(|manager| {
self.pointer.as_ref().unwrap(), super::pointer::implement_relative_pointer(
manager, self.sink.clone(),
) self.pointer.as_ref().unwrap(),
.ok() manager,
}) )
.ok()
})
} }
// destroy pointer if applicable // destroy pointer if applicable
if !capabilities.contains(wl_seat::Capability::Pointer) { if !capabilities.contains(wl_seat::Capability::Pointer) {
@ -603,17 +644,67 @@ impl<T> Drop for SeatData<T> {
* Monitor stuff * Monitor stuff
*/ */
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
}
}
}
#[derive(Clone)]
pub struct MonitorHandle { pub struct MonitorHandle {
pub(crate) proxy: wl_output::WlOutput, pub(crate) proxy: wl_output::WlOutput,
pub(crate) mgr: OutputMgr, pub(crate) mgr: OutputMgr,
} }
impl Clone for MonitorHandle { impl PartialEq for MonitorHandle {
fn clone(&self) -> MonitorHandle { fn eq(&self, other: &Self) -> bool {
MonitorHandle { self.native_identifier() == other.native_identifier()
proxy: self.proxy.clone(), }
mgr: self.mgr.clone(), }
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.native_identifier().cmp(&other.native_identifier())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.native_identifier().hash(state);
} }
} }
@ -680,15 +771,20 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let monitor = self.clone();
self.mgr self.mgr
.with_info(&self.proxy, |_, info| info.modes.clone()) .with_info(&self.proxy, |_, info| info.modes.clone())
.unwrap_or(vec![]) .unwrap_or(vec![])
.into_iter() .into_iter()
.map(|x| VideoMode { .map(move |x| RootVideoMode {
size: (x.dimensions.0 as u32, x.dimensions.1 as u32), video_mode: PlatformVideoMode::Wayland(VideoMode {
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16, size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
bit_depth: 32, refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: 32,
monitor: monitor.clone(),
}),
}) })
} }
} }

View file

@ -83,7 +83,17 @@ pub fn init_keyboard(
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ } KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
KbEvent::Modifiers { KbEvent::Modifiers {
modifiers: event_modifiers, modifiers: event_modifiers,
} => *modifiers_tracker.lock().unwrap() = event_modifiers.into(), } => {
let modifiers = event_modifiers.into();
*modifiers_tracker.lock().unwrap() = modifiers;
if let Some(wid) = *target.lock().unwrap() {
my_sink
.send((WindowEvent::ModifiersChanged { modifiers }, wid))
.unwrap();
}
}
} }
}, },
move |repeat_event: KeyRepeatEvent, _| { move |repeat_event: KeyRepeatEvent, _| {

View file

@ -3,7 +3,8 @@
pub use self::{ pub use self::{
event_loop::{ event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink, EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
WindowEventsSink,
}, },
window::Window, window::Window,
}; };

View file

@ -39,6 +39,7 @@ pub(crate) fn implement_touch<T: 'static>(
), ),
phase: TouchPhase::Started, phase: TouchPhase::Started,
location: (x, y).into(), location: (x, y).into(),
force: None, // TODO
id: id as u64, id: id as u64,
}), }),
wid, wid,
@ -61,6 +62,7 @@ pub(crate) fn implement_touch<T: 'static>(
), ),
phase: TouchPhase::Ended, phase: TouchPhase::Ended,
location: pt.location.into(), location: pt.location.into(),
force: None, // TODO
id: id as u64, id: id as u64,
}), }),
pt.wid, pt.wid,
@ -78,6 +80,7 @@ pub(crate) fn implement_touch<T: 'static>(
), ),
phase: TouchPhase::Moved, phase: TouchPhase::Moved,
location: (x, y).into(), location: (x, y).into(),
force: None, // TODO
id: id as u64, id: id as u64,
}), }),
pt.wid, pt.wid,
@ -94,6 +97,7 @@ pub(crate) fn implement_touch<T: 'static>(
), ),
phase: TouchPhase::Cancelled, phase: TouchPhase::Cancelled,
location: pt.location.into(), location: pt.location.into(),
force: None, // TODO
id: pt.id as u64, id: pt.id as u64,
}), }),
pt.wid, pt.wid,

View file

@ -1,3 +1,4 @@
use raw_window_handle::unix::WaylandHandle;
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
sync::{Arc, Mutex, Weak}, sync::{Arc, Mutex, Weak},
@ -8,10 +9,11 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle, monitor::MonitorHandle as RootMonitorHandle,
platform_impl::{ platform_impl::{
platform::wayland::event_loop::{available_monitors, primary_monitor},
MonitorHandle as PlatformMonitorHandle, MonitorHandle as PlatformMonitorHandle,
PlatformSpecificWindowBuilderAttributes as PlAttributes, PlatformSpecificWindowBuilderAttributes as PlAttributes,
}, },
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
}; };
use smithay_client_toolkit::{ use smithay_client_toolkit::{
@ -25,7 +27,6 @@ use smithay_client_toolkit::{
}; };
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId}; use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
pub struct Window { pub struct Window {
surface: wl_surface::WlSurface, surface: wl_surface::WlSurface,
@ -108,13 +109,19 @@ impl Window {
} }
// Check for fullscreen requirements // Check for fullscreen requirements
if let Some(RootMonitorHandle { match attributes.fullscreen {
inner: PlatformMonitorHandle::Wayland(ref monitor_id), Some(Fullscreen::Exclusive(_)) => {
}) = attributes.fullscreen panic!("Wayland doesn't support exclusive fullscreen")
{ }
frame.set_fullscreen(Some(&monitor_id.proxy)); Some(Fullscreen::Borderless(RootMonitorHandle {
} else if attributes.maximized { inner: PlatformMonitorHandle::Wayland(ref monitor_id),
frame.set_maximized(); })) => frame.set_fullscreen(Some(&monitor_id.proxy)),
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => {
if attributes.maximized {
frame.set_maximized();
}
}
} }
frame.set_resizable(attributes.resizable); frame.set_resizable(attributes.resizable);
@ -252,25 +259,31 @@ impl Window {
} }
} }
pub fn fullscreen(&self) -> Option<MonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
if *(self.fullscreen.lock().unwrap()) { if *(self.fullscreen.lock().unwrap()) {
Some(self.current_monitor()) Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
}))
} else { } else {
None None
} }
} }
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
if let Some(RootMonitorHandle { match fullscreen {
inner: PlatformMonitorHandle::Wayland(ref monitor_id), Some(Fullscreen::Exclusive(_)) => {
}) = monitor panic!("Wayland doesn't support exclusive fullscreen")
{ }
self.frame Some(Fullscreen::Borderless(RootMonitorHandle {
.lock() inner: PlatformMonitorHandle::Wayland(ref monitor_id),
.unwrap() })) => {
.set_fullscreen(Some(&monitor_id.proxy)); self.frame
} else { .lock()
self.frame.lock().unwrap().unset_fullscreen(); .unwrap()
.set_fullscreen(Some(&monitor_id.proxy));
}
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => self.frame.lock().unwrap().unset_fullscreen(),
} }
} }
@ -321,6 +334,14 @@ impl Window {
pub fn primary_monitor(&self) -> MonitorHandle { pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor(&self.outputs) primary_monitor(&self.outputs)
} }
pub fn raw_window_handle(&self) -> WaylandHandle {
WaylandHandle {
surface: self.surface().as_ref().c_ptr() as *mut _,
display: self.display().as_ref().c_ptr() as *mut _,
..WaylandHandle::empty()
}
}
} }
impl Drop for Window { impl Drop for Window {

View file

@ -8,6 +8,8 @@ use super::{
XExtension, XExtension,
}; };
use util::modifiers::{ModifierKeyState, ModifierKeymap};
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}, event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent},
@ -21,6 +23,9 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) devices: RefCell<HashMap<DeviceId, Device>>, pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension, pub(super) xi2ext: XExtension,
pub(super) target: Rc<RootELW<T>>, pub(super) target: Rc<RootELW<T>>,
pub(super) mod_keymap: ModifierKeymap,
pub(super) device_mod_state: ModifierKeyState,
pub(super) window_mod_state: ModifierKeyState,
} }
impl<T: 'static> EventProcessor<T> { impl<T: 'static> EventProcessor<T> {
@ -112,12 +117,22 @@ impl<T: 'static> EventProcessor<T> {
let event_type = xev.get_type(); let event_type = xev.get_type();
match event_type { match event_type {
ffi::MappingNotify => { ffi::MappingNotify => {
unsafe { let mapping: &ffi::XMappingEvent = xev.as_ref();
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
if mapping.request == ffi::MappingModifier
|| mapping.request == ffi::MappingKeyboard
{
unsafe {
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
}
wt.xconn
.check_errors()
.expect("Failed to call XRefreshKeyboardMapping");
self.mod_keymap.reset_from_x_connection(&wt.xconn);
self.device_mod_state.update(&self.mod_keymap);
self.window_mod_state.update(&self.mod_keymap);
} }
wt.xconn
.check_errors()
.expect("Failed to call XRefreshKeyboardMapping");
} }
ffi::ClientMessage => { ffi::ClientMessage => {
@ -374,7 +389,11 @@ impl<T: 'static> EventProcessor<T> {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size); let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
let new_hidpi_factor = monitor.hidpi_factor; let new_hidpi_factor = monitor.hidpi_factor;
shared_state_lock.last_monitor = Some(monitor.clone());
// Avoid caching an invalid dummy monitor handle
if monitor.id != 0 {
shared_state_lock.last_monitor = Some(monitor.clone());
}
new_hidpi_factor new_hidpi_factor
}; };
if last_hidpi_factor != new_hidpi_factor { if last_hidpi_factor != new_hidpi_factor {
@ -514,13 +533,6 @@ impl<T: 'static> EventProcessor<T> {
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
// a keycode of 0. // a keycode of 0.
if xkev.keycode != 0 { if xkev.keycode != 0 {
let modifiers = ModifiersState {
alt: xkev.state & ffi::Mod1Mask != 0,
shift: xkev.state & ffi::ShiftMask != 0,
ctrl: xkev.state & ffi::ControlMask != 0,
logo: xkev.state & ffi::Mod4Mask != 0,
};
let keysym = unsafe { let keysym = unsafe {
let mut keysym = 0; let mut keysym = 0;
(wt.xconn.xlib.XLookupString)( (wt.xconn.xlib.XLookupString)(
@ -535,6 +547,8 @@ impl<T: 'static> EventProcessor<T> {
}; };
let virtual_keycode = events::keysym_to_element(keysym as c_uint); let virtual_keycode = events::keysym_to_element(keysym as c_uint);
let modifiers = self.window_mod_state.modifiers();
callback(Event::WindowEvent { callback(Event::WindowEvent {
window_id, window_id,
event: WindowEvent::KeyboardInput { event: WindowEvent::KeyboardInput {
@ -547,6 +561,27 @@ impl<T: 'static> EventProcessor<T> {
}, },
}, },
}); });
if let Some(modifier) =
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
{
self.window_mod_state.key_event(
state,
xkev.keycode as ffi::KeyCode,
modifier,
);
let new_modifiers = self.window_mod_state.modifiers();
if modifiers != new_modifiers {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged {
modifiers: new_modifiers,
},
});
}
}
} }
if state == Pressed { if state == Pressed {
@ -859,6 +894,21 @@ impl<T: 'static> EventProcessor<T> {
event: Focused(true), event: Focused(true),
}); });
// When focus is gained, send any existing modifiers
// to the window in a ModifiersChanged event. This is
// done to compensate for modifier keys that may be
// changed while a window is out of focus.
if !self.device_mod_state.is_empty() {
self.window_mod_state = self.device_mod_state.clone();
let modifiers = self.window_mod_state.modifiers();
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged { modifiers },
});
}
// The deviceid for this event is for a keyboard instead of a pointer, // The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work. // so we have to do a little extra work.
let pointer_id = self let pointer_id = self
@ -890,6 +940,22 @@ impl<T: 'static> EventProcessor<T> {
.borrow_mut() .borrow_mut()
.unfocus(xev.event) .unfocus(xev.event)
.expect("Failed to unfocus input context"); .expect("Failed to unfocus input context");
// When focus is lost, send a ModifiersChanged event
// containing no modifiers set. This is done to compensate
// for modifier keys that may be changed while a window
// is out of focus.
if !self.window_mod_state.is_empty() {
self.window_mod_state.clear();
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: WindowEvent::ModifiersChanged {
modifiers: ModifiersState::default(),
},
});
}
callback(Event::WindowEvent { callback(Event::WindowEvent {
window_id: mkwid(xev.event), window_id: mkwid(xev.event),
event: Focused(false), event: Focused(false),
@ -918,6 +984,7 @@ impl<T: 'static> EventProcessor<T> {
device_id: mkdid(xev.deviceid), device_id: mkdid(xev.deviceid),
phase, phase,
location, location,
force: None, // TODO
id: xev.detail as u64, id: xev.detail as u64,
}), }),
}) })
@ -1021,18 +1088,25 @@ impl<T: 'static> EventProcessor<T> {
let virtual_keycode = events::keysym_to_element(keysym as c_uint); let virtual_keycode = events::keysym_to_element(keysym as c_uint);
if let Some(modifier) =
self.mod_keymap.get_modifier(keycode as ffi::KeyCode)
{
self.device_mod_state.key_event(
state,
keycode as ffi::KeyCode,
modifier,
);
}
let modifiers = self.device_mod_state.modifiers();
callback(Event::DeviceEvent { callback(Event::DeviceEvent {
device_id: mkdid(device_id), device_id: mkdid(device_id),
event: DeviceEvent::Key(KeyboardInput { event: DeviceEvent::Key(KeyboardInput {
scancode, scancode,
virtual_keycode, virtual_keycode,
state, state,
// So, in an ideal world we can use libxkbcommon to get modifiers. modifiers,
// However, libxkbcommon-x11 isn't as commonly installed as one
// would hope. We can still use the Xkb extension to get
// comprehensive keyboard state updates, but interpreting that
// info manually is going to be involved.
modifiers: ModifiersState::default(),
}), }),
}); });
} }

View file

@ -11,7 +11,7 @@ mod window;
mod xdisplay; mod xdisplay;
pub use self::{ pub use self::{
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::UnownedWindow, window::UnownedWindow,
xdisplay::{XConnection, XError, XNotSupported}, xdisplay::{XConnection, XError, XNotSupported},
}; };
@ -20,7 +20,7 @@ use std::{
cell::RefCell, cell::RefCell,
collections::{HashMap, HashSet, VecDeque}, collections::{HashMap, HashSet, VecDeque},
ffi::CStr, ffi::CStr,
mem, mem::{self, MaybeUninit},
ops::Deref, ops::Deref,
os::raw::*, os::raw::*,
rc::Rc, rc::Rc,
@ -34,6 +34,7 @@ use self::{
dnd::{Dnd, DndState}, dnd::{Dnd, DndState},
event_processor::EventProcessor, event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender}, ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
util::modifiers::ModifierKeymap,
}; };
use crate::{ use crate::{
error::OsError as RootOsError, error::OsError as RootOsError,
@ -60,16 +61,24 @@ pub struct EventLoop<T: 'static> {
_x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>, _x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>,
_user_source: ::calloop::Source<::calloop::channel::Channel<T>>, _user_source: ::calloop::Source<::calloop::channel::Channel<T>>,
pending_user_events: Rc<RefCell<VecDeque<T>>>, pending_user_events: Rc<RefCell<VecDeque<T>>>,
event_processor: Rc<RefCell<EventProcessor<T>>>,
user_sender: ::calloop::channel::Sender<T>, user_sender: ::calloop::channel::Sender<T>,
pending_events: Rc<RefCell<VecDeque<Event<T>>>>, pending_events: Rc<RefCell<VecDeque<Event<T>>>>,
target: Rc<RootELW<T>>, pub(crate) target: Rc<RootELW<T>>,
} }
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> { pub struct EventLoopProxy<T: 'static> {
user_sender: ::calloop::channel::Sender<T>, user_sender: ::calloop::channel::Sender<T>,
} }
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_sender: self.user_sender.clone(),
}
}
}
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> { pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
@ -100,22 +109,21 @@ impl<T: 'static> EventLoop<T> {
.expect("Failed to query XRandR extension"); .expect("Failed to query XRandR extension");
let xi2ext = unsafe { let xi2ext = unsafe {
let mut result = XExtension { let mut ext = XExtension::default();
opcode: mem::uninitialized(),
first_event_id: mem::uninitialized(),
first_error_id: mem::uninitialized(),
};
let res = (xconn.xlib.XQueryExtension)( let res = (xconn.xlib.XQueryExtension)(
xconn.display, xconn.display,
b"XInputExtension\0".as_ptr() as *const c_char, b"XInputExtension\0".as_ptr() as *const c_char,
&mut result.opcode as *mut c_int, &mut ext.opcode,
&mut result.first_event_id as *mut c_int, &mut ext.first_event_id,
&mut result.first_error_id as *mut c_int, &mut ext.first_error_id,
); );
if res == ffi::False { if res == ffi::False {
panic!("X server missing XInput extension"); panic!("X server missing XInput extension");
} }
result
ext
}; };
unsafe { unsafe {
@ -136,6 +144,9 @@ impl<T: 'static> EventLoop<T> {
xconn.update_cached_wm_info(root); xconn.update_cached_wm_info(root);
let mut mod_keymap = ModifierKeymap::new();
mod_keymap.reset_from_x_connection(&xconn);
let target = Rc::new(RootELW { let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
ime, ime,
@ -172,13 +183,16 @@ impl<T: 'static> EventLoop<T> {
// Handle X11 events // Handle X11 events
let pending_events: Rc<RefCell<VecDeque<_>>> = Default::default(); let pending_events: Rc<RefCell<VecDeque<_>>> = Default::default();
let mut processor = EventProcessor { let processor = EventProcessor {
target: target.clone(), target: target.clone(),
dnd, dnd,
devices: Default::default(), devices: Default::default(),
randr_event_offset, randr_event_offset,
ime_receiver, ime_receiver,
xi2ext, xi2ext,
mod_keymap,
device_mod_state: Default::default(),
window_mod_state: Default::default(),
}; };
// Register for device hotplug events // Register for device hotplug events
@ -190,6 +204,9 @@ impl<T: 'static> EventLoop<T> {
processor.init_device(ffi::XIAllDevices); processor.init_device(ffi::XIAllDevices);
let processor = Rc::new(RefCell::new(processor));
let event_processor = processor.clone();
// Setup the X11 event source // Setup the X11 event source
let mut x11_events = let mut x11_events =
::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd); ::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd);
@ -198,16 +215,11 @@ impl<T: 'static> EventLoop<T> {
.handle() .handle()
.insert_source(x11_events, { .insert_source(x11_events, {
let pending_events = pending_events.clone(); let pending_events = pending_events.clone();
let mut callback = move |event| {
pending_events.borrow_mut().push_back(event);
};
move |evt, &mut ()| { move |evt, &mut ()| {
if evt.readiness.is_readable() { if evt.readiness.is_readable() {
// process all pending events let mut processor = processor.borrow_mut();
let mut xev = unsafe { mem::uninitialized() }; let mut pending_events = pending_events.borrow_mut();
while unsafe { processor.poll_one_event(&mut xev) } { drain_events(&mut processor, &mut pending_events);
processor.process_event(&mut xev, &mut callback);
}
} }
} }
}) })
@ -220,18 +232,13 @@ impl<T: 'static> EventLoop<T> {
_user_source, _user_source,
user_sender, user_sender,
pending_user_events, pending_user_events,
event_processor,
target, target,
}; };
result result
} }
/// Returns the `XConnection` of this events loop.
#[inline]
pub fn x_connection(&self) -> &Arc<XConnection> {
&get_xtarget(&self.target).xconn
}
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy { EventLoopProxy {
user_sender: self.user_sender.clone(), user_sender: self.user_sender.clone(),
@ -249,6 +256,12 @@ impl<T: 'static> EventLoop<T> {
let mut control_flow = ControlFlow::default(); let mut control_flow = ControlFlow::default();
let wt = get_xtarget(&self.target); let wt = get_xtarget(&self.target);
callback(
crate::event::Event::NewEvents(crate::event::StartCause::Init),
&self.target,
&mut control_flow,
);
loop { loop {
// Empty the event buffer // Empty the event buffer
{ {
@ -272,8 +285,10 @@ impl<T: 'static> EventLoop<T> {
} }
// Empty the redraw requests // Empty the redraw requests
{ {
let mut guard = wt.pending_redraws.lock().unwrap(); // Release the lock to prevent deadlock
for wid in guard.drain() { let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect();
for wid in windows {
sticky_exit_callback( sticky_exit_callback(
Event::WindowEvent { Event::WindowEvent {
window_id: crate::window::WindowId(super::WindowId::X(wid)), window_id: crate::window::WindowId(super::WindowId::X(wid)),
@ -354,6 +369,10 @@ impl<T: 'static> EventLoop<T> {
} }
} }
} }
// If the user callback had any interaction with the X server,
// it may have received and buffered some user input events.
self.drain_events();
} }
callback( callback(
@ -370,13 +389,43 @@ impl<T: 'static> EventLoop<T> {
self.run_return(callback); self.run_return(callback);
::std::process::exit(0); ::std::process::exit(0);
} }
fn drain_events(&self) {
let mut processor = self.event_processor.borrow_mut();
let mut pending_events = self.pending_events.borrow_mut();
drain_events(&mut processor, &mut pending_events);
}
} }
fn get_xtarget<T>(rt: &RootELW<T>) -> &EventLoopWindowTarget<T> { fn drain_events<T: 'static>(
if let super::EventLoopWindowTarget::X(ref target) = rt.p { processor: &mut EventProcessor<T>,
target pending_events: &mut VecDeque<Event<T>>,
} else { ) {
unreachable!(); let mut callback = |event| {
pending_events.push_back(event);
};
// process all pending events
let mut xev = MaybeUninit::uninit();
while unsafe { processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
processor.process_event(&mut xev, &mut callback);
}
}
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
match target.p {
super::EventLoopWindowTarget::X(ref target) => target,
_ => unreachable!(),
}
}
impl<T> EventLoopWindowTarget<T> {
/// Returns the `XConnection` of this events loop.
#[inline]
pub fn x_connection(&self) -> &Arc<XConnection> {
&self.xconn
} }
} }
@ -395,19 +444,19 @@ struct DeviceInfo<'a> {
impl<'a> DeviceInfo<'a> { impl<'a> DeviceInfo<'a> {
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> { fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
unsafe { unsafe {
let mut count = mem::uninitialized(); let mut count = 0;
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count); let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
xconn.check_errors().ok().and_then(|_| { xconn.check_errors().ok()?;
if info.is_null() || count == 0 {
None if info.is_null() || count == 0 {
} else { None
Some(DeviceInfo { } else {
xconn, Some(DeviceInfo {
info, xconn,
count: count as usize, info,
}) count: count as usize,
} })
}) }
} }
} }
} }
@ -512,7 +561,7 @@ impl<'a> Drop for GenericEventCookie<'a> {
} }
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
struct XExtension { struct XExtension {
opcode: c_int, opcode: c_int,
first_event_id: c_int, first_event_id: c_int,

View file

@ -4,47 +4,66 @@ use parking_lot::Mutex;
use super::{ use super::{
ffi::{ ffi::{
RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window, RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
XRRScreenResources, RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
}, },
util, XConnection, XError, util, XConnection, XError,
}; };
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
}; };
// Used to test XRandR < 1.5 code path. This should always be committed as false. // Used for testing. This should always be committed as false.
const FORCE_RANDR_COMPAT: bool = false;
// Also used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false; const DISABLE_MONITOR_LIST_CACHING: bool = false;
lazy_static! { lazy_static! {
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default(); static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
} }
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
if avail_major == major {
avail_minor >= minor
} else {
avail_major > major
}
} else {
unreachable!();
}
}
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> { pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
// We update this lazily. // We update this lazily.
(*MONITORS.lock()).take() (*MONITORS.lock()).take()
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) native_mode: RRMode,
pub(crate) monitor: Option<MonitorHandle>,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::X(self.monitor.clone().unwrap()),
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MonitorHandle { pub struct MonitorHandle {
/// The actual id /// The actual id
id: u32, pub(crate) id: RRCrtc,
/// The name of the monitor /// The name of the monitor
pub(crate) name: String, pub(crate) name: String,
/// The size of the monitor /// The size of the monitor
@ -61,16 +80,43 @@ pub struct MonitorHandle {
video_modes: Vec<VideoMode>, video_modes: Vec<VideoMode>,
} }
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.id.cmp(&other.id)
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl MonitorHandle { impl MonitorHandle {
fn from_repr( fn new(
xconn: &XConnection, xconn: &XConnection,
resources: *mut XRRScreenResources, resources: *mut XRRScreenResources,
id: u32, id: RRCrtc,
repr: util::MonitorRepr, crtc: *mut XRRCrtcInfo,
primary: bool, primary: bool,
) -> Option<Self> { ) -> Option<Self> {
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? }; let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let (dimensions, position) = unsafe { (repr.size(), repr.position()) }; let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
let rect = util::AaRect::new(position, dimensions); let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle { Some(MonitorHandle {
id, id,
@ -84,6 +130,19 @@ impl MonitorHandle {
}) })
} }
fn dummy() -> Self {
MonitorHandle {
id: 0,
name: "<dummy monitor>".into(),
hidpi_factor: 1.0,
dimensions: (1, 1),
position: (0, 0),
primary: true,
rect: util::AaRect::new((0, 0), (1, 1)),
video_modes: Vec::new(),
}
}
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
Some(self.name.clone()) Some(self.name.clone())
} }
@ -107,17 +166,27 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
self.video_modes.clone().into_iter() let monitor = self.clone();
self.video_modes.clone().into_iter().map(move |mut x| {
x.monitor = Some(monitor.clone());
RootVideoMode {
video_mode: PlatformVideoMode::X(x),
}
})
} }
} }
impl XConnection { impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle { pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
let monitors = self.available_monitors(); let monitors = self.available_monitors();
let default = monitors
.get(0) if monitors.is_empty() {
.expect("[winit] Failed to find any monitors using XRandR."); // Return a dummy monitor to avoid panicking
return MonitorHandle::dummy();
}
let default = monitors.get(0).unwrap();
let window_rect = match window_rect { let window_rect = match window_rect {
Some(rect) => rect, Some(rect) => rect,
@ -139,8 +208,12 @@ impl XConnection {
fn query_monitor_list(&self) -> Vec<MonitorHandle> { fn query_monitor_list(&self) -> Vec<MonitorHandle> {
unsafe { unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display); let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if version_is_at_least(1, 3) { let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else { } else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms. // WARNING: this function is supposedly very slow, on the order of hundreds of ms.
@ -155,48 +228,19 @@ impl XConnection {
let mut available; let mut available;
let mut has_primary = false; let mut has_primary = false;
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT { let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and available = Vec::with_capacity((*resources).ncrtc as usize);
// videowalls. for crtc_index in 0..(*resources).ncrtc {
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap(); let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let mut monitor_count = 0; let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let monitors = let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
(xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count); if is_active {
assert!(monitor_count >= 0); let is_primary = *(*crtc).outputs.offset(0) == primary;
available = Vec::with_capacity(monitor_count as usize);
for monitor_index in 0..monitor_count {
let monitor = monitors.offset(monitor_index as isize);
let is_primary = (*monitor).primary != 0;
has_primary |= is_primary; has_primary |= is_primary;
MonitorHandle::from_repr( MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
self, .map(|monitor_id| available.push(monitor_id));
resources,
monitor_index as u32,
monitor.into(),
is_primary,
)
.map(|monitor_id| available.push(monitor_id));
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
// videowall setups will also show monitors that aren't in the logical groups the user
// cares about.
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let crtc = util::MonitorRepr::from(crtc);
let is_primary = crtc.get_output() == primary;
has_primary |= is_primary;
MonitorHandle::from_repr(self, resources, crtc_id as u32, crtc, is_primary)
.map(|monitor_id| available.push(monitor_id));
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
} }
(self.xrandr.XRRFreeCrtcInfo)(crtc);
} }
// If no monitors were detected as being primary, we just pick one ourselves! // If no monitors were detected as being primary, we just pick one ourselves!
@ -232,23 +276,19 @@ impl XConnection {
self.available_monitors() self.available_monitors()
.into_iter() .into_iter()
.find(|monitor| monitor.primary) .find(|monitor| monitor.primary)
.expect("[winit] Failed to find any monitors using XRandR.") .unwrap_or_else(MonitorHandle::dummy)
} }
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> { pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
{ let has_xrandr = unsafe {
let mut version_lock = XRANDR_VERSION.lock(); let mut major = 0;
if version_lock.is_none() { let mut minor = 0;
let mut major = 0; (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
let mut minor = 0; };
let has_extension = assert!(
unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) }; has_xrandr == True,
if has_extension != True { "[winit] XRandR extension not available."
panic!("[winit] XRandR extension not available."); );
}
*version_lock = Some((major, minor));
}
}
let mut event_offset = 0; let mut event_offset = 0;
let mut error_offset = 0; let mut error_offset = 0;

View file

@ -30,13 +30,17 @@ impl XConnection {
event_mask: Option<c_long>, event_mask: Option<c_long>,
data: ClientMsgPayload, data: ClientMsgPayload,
) -> Flusher<'_> { ) -> Flusher<'_> {
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() }; let event = ffi::XClientMessageEvent {
event.type_ = ffi::ClientMessage; type_: ffi::ClientMessage,
event.display = self.display; display: self.display,
event.window = window; window,
event.message_type = message_type; message_type,
event.format = c_long::FORMAT as c_int; format: c_long::FORMAT as c_int,
event.data = unsafe { mem::transmute(data) }; data: unsafe { mem::transmute(data) },
// These fields are ignored by `XSendEvent`
serial: 0,
send_event: 0,
};
self.send_event(target_window, event_mask, event) self.send_event(target_window, event_mask, event)
} }
@ -54,12 +58,17 @@ impl XConnection {
let format = T::FORMAT; let format = T::FORMAT;
let size_of_t = mem::size_of::<T>(); let size_of_t = mem::size_of::<T>();
debug_assert_eq!(size_of_t, format.get_actual_size()); debug_assert_eq!(size_of_t, format.get_actual_size());
let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() }; let mut event = ffi::XClientMessageEvent {
event.type_ = ffi::ClientMessage; type_: ffi::ClientMessage,
event.display = self.display; display: self.display,
event.window = window; window,
event.message_type = message_type; message_type,
event.format = format as c_int; format: format as c_int,
data: ffi::ClientMessageData::new(),
// These fields are ignored by `XSendEvent`
serial: 0,
send_event: 0,
};
let t_per_payload = format.get_payload_size() / size_of_t; let t_per_payload = format.get_payload_size() / size_of_t;
assert!(t_per_payload > 0); assert!(t_per_payload > 0);

View file

@ -0,0 +1,129 @@
use crate::window::CursorIcon;
use super::*;
impl XConnection {
pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option<CursorIcon>) {
let cursor = *self
.cursor_cache
.lock()
.entry(cursor)
.or_insert_with(|| self.get_cursor(cursor));
self.update_cursor(window, cursor);
}
fn create_empty_cursor(&self) -> ffi::Cursor {
let data = 0;
let pixmap = unsafe {
let screen = (self.xlib.XDefaultScreen)(self.display);
let window = (self.xlib.XRootWindow)(self.display, screen);
(self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1)
};
if pixmap == 0 {
panic!("failed to allocate pixmap for cursor");
}
unsafe {
// We don't care about this color, since it only fills bytes
// in the pixmap which are not 0 in the mask.
let mut dummy_color = MaybeUninit::uninit();
let cursor = (self.xlib.XCreatePixmapCursor)(
self.display,
pixmap,
pixmap,
dummy_color.as_mut_ptr(),
dummy_color.as_mut_ptr(),
0,
0,
);
(self.xlib.XFreePixmap)(self.display, pixmap);
cursor
}
}
fn load_cursor(&self, name: &[u8]) -> ffi::Cursor {
unsafe {
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
}
}
fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor {
for name in names.iter() {
let xcursor = self.load_cursor(name);
if xcursor != 0 {
return xcursor;
}
}
0
}
fn get_cursor(&self, cursor: Option<CursorIcon>) -> ffi::Cursor {
let cursor = match cursor {
Some(cursor) => cursor,
None => return self.create_empty_cursor(),
};
let load = |name: &[u8]| self.load_cursor(name);
let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names);
// Try multiple names in some cases where the name
// differs on the desktop environments or themes.
//
// Try the better looking (or more suiting) names first.
match cursor {
CursorIcon::Alias => load(b"link\0"),
CursorIcon::Arrow => load(b"arrow\0"),
CursorIcon::Cell => load(b"plus\0"),
CursorIcon::Copy => load(b"copy\0"),
CursorIcon::Crosshair => load(b"crosshair\0"),
CursorIcon::Default => load(b"left_ptr\0"),
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
CursorIcon::Help => load(b"question_arrow\0"),
CursorIcon::Move => load(b"move\0"),
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
CursorIcon::Progress => load(b"left_ptr_watch\0"),
CursorIcon::AllScroll => load(b"all-scroll\0"),
CursorIcon::ContextMenu => load(b"context-menu\0"),
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
// Resize cursors
CursorIcon::EResize => load(b"right_side\0"),
CursorIcon::NResize => load(b"top_side\0"),
CursorIcon::NeResize => load(b"top_right_corner\0"),
CursorIcon::NwResize => load(b"top_left_corner\0"),
CursorIcon::SResize => load(b"bottom_side\0"),
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
CursorIcon::WResize => load(b"left_side\0"),
CursorIcon::EwResize => load(b"h_double_arrow\0"),
CursorIcon::NsResize => load(b"v_double_arrow\0"),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
CursorIcon::VerticalText => load(b"vertical-text\0"),
CursorIcon::Wait => load(b"watch\0"),
CursorIcon::ZoomIn => load(b"zoom-in\0"),
CursorIcon::ZoomOut => load(b"zoom-out\0"),
}
}
fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) {
unsafe {
(self.xlib.XDefineCursor)(self.display, window, cursor);
self.flush_requests().expect("Failed to set the cursor");
}
}
}

View file

@ -41,14 +41,14 @@ impl AaRect {
} }
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct TranslatedCoords { pub struct TranslatedCoords {
pub x_rel_root: c_int, pub x_rel_root: c_int,
pub y_rel_root: c_int, pub y_rel_root: c_int,
pub child: ffi::Window, pub child: ffi::Window,
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Geometry { pub struct Geometry {
pub root: ffi::Window, pub root: ffi::Window,
// If you want positions relative to the root window, use translate_coords. // If you want positions relative to the root window, use translate_coords.
@ -183,7 +183,8 @@ impl XConnection {
window: ffi::Window, window: ffi::Window,
root: ffi::Window, root: ffi::Window,
) -> Result<TranslatedCoords, XError> { ) -> Result<TranslatedCoords, XError> {
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() }; let mut coords = TranslatedCoords::default();
unsafe { unsafe {
(self.xlib.XTranslateCoordinates)( (self.xlib.XTranslateCoordinates)(
self.display, self.display,
@ -191,18 +192,20 @@ impl XConnection {
root, root,
0, 0,
0, 0,
&mut translated_coords.x_rel_root, &mut coords.x_rel_root,
&mut translated_coords.y_rel_root, &mut coords.y_rel_root,
&mut translated_coords.child, &mut coords.child,
); );
} }
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
self.check_errors().map(|_| translated_coords) self.check_errors()?;
Ok(coords)
} }
// This is adequate for inner_size // This is adequate for inner_size
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> { pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
let mut geometry: Geometry = unsafe { mem::uninitialized() }; let mut geometry = Geometry::default();
let _status = unsafe { let _status = unsafe {
(self.xlib.XGetGeometry)( (self.xlib.XGetGeometry)(
self.display, self.display,
@ -216,8 +219,9 @@ impl XConnection {
&mut geometry.depth, &mut geometry.depth,
) )
}; };
//println!("XGetGeometry geo:{:?}", geometry);
self.check_errors().map(|_| geometry) self.check_errors()?;
Ok(geometry)
} }
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> { fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
@ -264,10 +268,10 @@ impl XConnection {
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> { fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
let parent = unsafe { let parent = unsafe {
let mut root: ffi::Window = mem::uninitialized(); let mut root = 0;
let mut parent: ffi::Window = mem::uninitialized(); let mut parent = 0;
let mut children: *mut ffi::Window = ptr::null_mut(); let mut children: *mut ffi::Window = ptr::null_mut();
let mut nchildren: c_uint = mem::uninitialized(); let mut nchildren = 0;
// What's filled into `parent` if `window` is the root window? // What's filled into `parent` if `window` is the root window?
let _status = (self.xlib.XQueryTree)( let _status = (self.xlib.XQueryTree)(

View file

@ -72,21 +72,21 @@ impl Default for WindowType {
impl WindowType { impl WindowType {
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom { pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
use self::WindowType::*; use self::WindowType::*;
let atom_name: &[u8] = match self { let atom_name: &[u8] = match *self {
&Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0", Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
&Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0", Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
&Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0", Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
&Menu => b"_NET_WM_WINDOW_TYPE_MENU\0", Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
&Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0", Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
&Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0", Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
&Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0", Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
&DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0", DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
&PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0", PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
&Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0", Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
&Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0", Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
&Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0", Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
&Dnd => b"_NET_WM_WINDOW_TYPE_DND\0", Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
&Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0", Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
}; };
unsafe { xconn.get_atom_unchecked(atom_name) } unsafe { xconn.get_atom_unchecked(atom_name) }
} }
@ -317,13 +317,13 @@ impl XConnection {
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> { pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
let size_hints = self.alloc_size_hints(); let size_hints = self.alloc_size_hints();
let mut supplied_by_user: c_long = unsafe { mem::uninitialized() }; let mut supplied_by_user = MaybeUninit::uninit();
unsafe { unsafe {
(self.xlib.XGetWMNormalHints)( (self.xlib.XGetWMNormalHints)(
self.display, self.display,
window, window,
size_hints.ptr, size_hints.ptr,
&mut supplied_by_user, supplied_by_user.as_mut_ptr(),
); );
} }
self.check_errors().map(|_| NormalHints { size_hints }) self.check_errors().map(|_| NormalHints { size_hints })

View file

@ -1,4 +1,4 @@
use std::str; use std::{slice, str};
use super::*; use super::*;
use crate::event::ModifiersState; use crate::event::ModifiersState;
@ -23,18 +23,19 @@ impl From<ffi::XIModifierState> for ModifiersState {
} }
} }
// NOTE: Some of these fields are not used, but may be of use in the future.
pub struct PointerState<'a> { pub struct PointerState<'a> {
xconn: &'a XConnection, xconn: &'a XConnection,
root: ffi::Window, pub root: ffi::Window,
child: ffi::Window, pub child: ffi::Window,
pub root_x: c_double, pub root_x: c_double,
pub root_y: c_double, pub root_y: c_double,
win_x: c_double, pub win_x: c_double,
win_y: c_double, pub win_y: c_double,
buttons: ffi::XIButtonState, buttons: ffi::XIButtonState,
modifiers: ffi::XIModifierState, modifiers: ffi::XIModifierState,
group: ffi::XIGroupState, pub group: ffi::XIGroupState,
relative_to_window: bool, pub relative_to_window: bool,
} }
impl<'a> PointerState<'a> { impl<'a> PointerState<'a> {
@ -93,29 +94,46 @@ impl XConnection {
device_id: c_int, device_id: c_int,
) -> Result<PointerState<'_>, XError> { ) -> Result<PointerState<'_>, XError> {
unsafe { unsafe {
let mut pointer_state: PointerState<'_> = mem::uninitialized(); let mut root = 0;
pointer_state.xconn = self; let mut child = 0;
pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)( let mut root_x = 0.0;
let mut root_y = 0.0;
let mut win_x = 0.0;
let mut win_y = 0.0;
let mut buttons = Default::default();
let mut modifiers = Default::default();
let mut group = Default::default();
let relative_to_window = (self.xinput2.XIQueryPointer)(
self.display, self.display,
device_id, device_id,
window, window,
&mut pointer_state.root, &mut root,
&mut pointer_state.child, &mut child,
&mut pointer_state.root_x, &mut root_x,
&mut pointer_state.root_y, &mut root_y,
&mut pointer_state.win_x, &mut win_x,
&mut pointer_state.win_y, &mut win_y,
&mut pointer_state.buttons, &mut buttons,
&mut pointer_state.modifiers, &mut modifiers,
&mut pointer_state.group, &mut group,
) == ffi::True; ) == ffi::True;
if let Err(err) = self.check_errors() {
// Running the destrutor would be bad news for us... self.check_errors()?;
mem::forget(pointer_state);
Err(err) Ok(PointerState {
} else { xconn: self,
Ok(pointer_state) root,
} child,
root_x,
root_y,
win_x,
win_y,
buttons,
modifiers,
group,
relative_to_window,
})
} }
} }
@ -123,7 +141,8 @@ impl XConnection {
&self, &self,
ic: ffi::XIC, ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent, key_event: &mut ffi::XKeyEvent,
buffer: &mut [u8], buffer: *mut u8,
size: usize,
) -> (ffi::KeySym, ffi::Status, c_int) { ) -> (ffi::KeySym, ffi::Status, c_int) {
let mut keysym: ffi::KeySym = 0; let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0; let mut status: ffi::Status = 0;
@ -131,8 +150,8 @@ impl XConnection {
(self.xlib.Xutf8LookupString)( (self.xlib.Xutf8LookupString)(
ic, ic,
key_event, key_event,
buffer.as_mut_ptr() as *mut c_char, buffer as *mut c_char,
buffer.len() as c_int, size as c_int,
&mut keysym, &mut keysym,
&mut status, &mut status,
) )
@ -141,21 +160,28 @@ impl XConnection {
} }
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() }; // `assume_init` is safe here because the array consists of `MaybeUninit` values,
let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer); // which do not require initialization.
// The buffer overflowed, so we'll make a new one on the heap. let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
if status == ffi::XBufferOverflow { unsafe { MaybeUninit::uninit().assume_init() };
let mut buffer = Vec::with_capacity(count as usize); // If the buffer overflows, we'll make a new one on the heap.
unsafe { buffer.set_len(count as usize) }; let mut vec;
let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer);
let (_, status, count) =
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
let bytes = if status == ffi::XBufferOverflow {
vec = Vec::with_capacity(count as usize);
let (_, _, new_count) =
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
debug_assert_eq!(count, new_count); debug_assert_eq!(count, new_count);
str::from_utf8(&buffer[..count as usize])
.unwrap_or("") unsafe { vec.set_len(count as usize) };
.to_string() &vec[..count as usize]
} else { } else {
str::from_utf8(&buffer[..count as usize]) unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
.unwrap_or("") };
.to_string()
} str::from_utf8(bytes).unwrap_or("").to_string()
} }
} }

View file

@ -3,12 +3,14 @@
mod atom; mod atom;
mod client_msg; mod client_msg;
mod cursor;
mod format; mod format;
mod geometry; mod geometry;
mod hint; mod hint;
mod icon; mod icon;
mod input; mod input;
mod memory; mod memory;
pub mod modifiers;
mod randr; mod randr;
mod window_property; mod window_property;
mod wm; mod wm;
@ -18,7 +20,12 @@ pub use self::{
randr::*, window_property::*, wm::*, randr::*, window_property::*, wm::*,
}; };
use std::{mem, ops::BitAnd, os::raw::*, ptr}; use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
ptr,
};
use super::{ffi, XConnection, XError}; use super::{ffi, XConnection, XError};

View file

@ -0,0 +1,149 @@
use std::{collections::HashMap, slice};
use super::*;
use crate::event::{ElementState, ModifiersState};
// Offsets within XModifierKeymap to each set of keycodes.
// We are only interested in Shift, Control, Alt, and Logo.
//
// There are 8 sets total. The order of keycode sets is:
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
//
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
const SHIFT_OFFSET: usize = 0;
const CONTROL_OFFSET: usize = 2;
const ALT_OFFSET: usize = 3;
const LOGO_OFFSET: usize = 6;
const NUM_MODS: usize = 8;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Modifier {
Alt,
Ctrl,
Shift,
Logo,
}
#[derive(Debug, Default)]
pub struct ModifierKeymap {
// Maps keycodes to modifiers
keys: HashMap<ffi::KeyCode, Modifier>,
}
#[derive(Clone, Debug, Default)]
pub struct ModifierKeyState {
// Contains currently pressed modifier keys and their corresponding modifiers
keys: HashMap<ffi::KeyCode, Modifier>,
}
impl ModifierKeymap {
pub fn new() -> ModifierKeymap {
ModifierKeymap::default()
}
pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option<Modifier> {
self.keys.get(&keycode).cloned()
}
pub fn reset_from_x_connection(&mut self, xconn: &XConnection) {
unsafe {
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
if keymap.is_null() {
panic!("failed to allocate XModifierKeymap");
}
self.reset_from_x_keymap(&*keymap);
(xconn.xlib.XFreeModifiermap)(keymap);
}
}
pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) {
let keys_per_mod = keymap.max_keypermod as usize;
let keys = unsafe {
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
};
self.keys.clear();
self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift);
self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl);
self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt);
self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo);
}
fn read_x_keys(
&mut self,
keys: &[ffi::KeyCode],
offset: usize,
keys_per_mod: usize,
modifier: Modifier,
) {
let start = offset * keys_per_mod;
let end = start + keys_per_mod;
for &keycode in &keys[start..end] {
if keycode != 0 {
self.keys.insert(keycode, modifier);
}
}
}
}
impl ModifierKeyState {
pub fn clear(&mut self) {
self.keys.clear();
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub fn update(&mut self, mods: &ModifierKeymap) {
self.keys.retain(|k, v| {
if let Some(m) = mods.get_modifier(*k) {
*v = m;
true
} else {
false
}
});
}
pub fn modifiers(&self) -> ModifiersState {
let mut state = ModifiersState::default();
for &m in self.keys.values() {
set_modifier(&mut state, m);
}
state
}
pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) {
match state {
ElementState::Pressed => self.key_press(keycode, modifier),
ElementState::Released => self.key_release(keycode),
}
}
pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) {
self.keys.insert(keycode, modifier);
}
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
self.keys.remove(&keycode);
}
}
fn set_modifier(state: &mut ModifiersState, modifier: Modifier) {
match modifier {
Modifier::Alt => state.alt = true,
Modifier::Ctrl => state.ctrl = true,
Modifier::Shift => state.shift = true,
Modifier::Logo => state.logo = true,
}
}

View file

@ -1,7 +1,10 @@
use std::{env, slice, str::FromStr}; use std::{env, slice, str::FromStr};
use super::*; use super::{
use crate::{dpi::validate_hidpi_factor, monitor::VideoMode}; ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
pub fn calc_dpi_factor( pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32), (width_px, height_px): (u32, u32),
@ -34,47 +37,6 @@ pub fn calc_dpi_factor(
dpi_factor dpi_factor
} }
pub enum MonitorRepr {
Monitor(*mut ffi::XRRMonitorInfo),
Crtc(*mut ffi::XRRCrtcInfo),
}
impl MonitorRepr {
pub unsafe fn get_output(&self) -> ffi::RROutput {
match *self {
// Same member names, but different locations within the struct...
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)),
}
}
pub unsafe fn size(&self) -> (u32, u32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
}
}
pub unsafe fn position(&self) -> (i32, i32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
}
}
}
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
MonitorRepr::Monitor(monitor)
}
}
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
MonitorRepr::Crtc(crtc)
}
}
impl XConnection { impl XConnection {
// Retrieve DPI from Xft.dpi property // Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> { pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
@ -96,11 +58,11 @@ impl XConnection {
} }
pub unsafe fn get_output_info( pub unsafe fn get_output_info(
&self, &self,
resources: *mut ffi::XRRScreenResources, resources: *mut XRRScreenResources,
repr: &MonitorRepr, crtc: *mut XRRCrtcInfo,
) -> Option<(String, f64, Vec<VideoMode>)> { ) -> Option<(String, f64, Vec<VideoMode>)> {
let output_info = let output_info =
(self.xrandr.XRRGetOutputInfo)(self.display, resources, repr.get_output()); (self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0));
if output_info.is_null() { if output_info.is_null() {
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) // When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
// it's possible for it to return null. // it's possible for it to return null.
@ -132,6 +94,10 @@ impl XConnection {
size: (x.width, x.height), size: (x.width, x.height),
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16, refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: bit_depth as u16, bit_depth: bit_depth as u16,
native_mode: x.id,
// This is populated in `MonitorHandle::video_modes` as the
// video mode is returned to the user
monitor: None,
} }
}); });
@ -144,7 +110,7 @@ impl XConnection {
dpi / 96. dpi / 96.
} else { } else {
calc_dpi_factor( calc_dpi_factor(
repr.size(), ((*crtc).width as u32, (*crtc).height as u32),
( (
(*output_info).mm_width as u64, (*output_info).mm_width as u64,
(*output_info).mm_height as u64, (*output_info).mm_height as u64,
@ -155,4 +121,61 @@ impl XConnection {
(self.xrandr.XRRFreeOutputInfo)(output_info); (self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, hidpi_factor, modes.collect())) Some((name, hidpi_factor, modes.collect()))
} }
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let status = (self.xrandr.XRRSetCrtcConfig)(
self.display,
resources,
crtc_id,
CurrentTime,
(*crtc).x,
(*crtc).y,
mode_id,
(*crtc).rotation,
(*crtc).outputs.offset(0),
1,
);
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
if status == Success as i32 {
Ok(())
} else {
Err(())
}
}
}
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let mode = (*crtc).mode;
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
mode
}
}
} }

View file

@ -43,13 +43,14 @@ impl XConnection {
let mut offset = 0; let mut offset = 0;
let mut done = false; let mut done = false;
let mut actual_type = 0;
let mut actual_format = 0;
let mut quantity_returned = 0;
let mut bytes_after = 0;
let mut buf: *mut c_uchar = ptr::null_mut();
while !done { while !done {
unsafe { unsafe {
let mut actual_type: ffi::Atom = mem::uninitialized();
let mut actual_format: c_int = mem::uninitialized();
let mut quantity_returned: c_ulong = mem::uninitialized();
let mut bytes_after: c_ulong = mem::uninitialized();
let mut buf: *mut c_uchar = ptr::null_mut();
(self.xlib.XGetWindowProperty)( (self.xlib.XGetWindowProperty)(
self.display, self.display,
window, window,

View file

@ -1,4 +1,15 @@
use std::{cmp, collections::HashSet, env, ffi::CString, mem, os::raw::*, path::Path, sync::Arc}; use raw_window_handle::unix::X11Handle;
use std::{
cmp,
collections::HashSet,
env,
ffi::CString,
mem::{self, replace, MaybeUninit},
os::raw::*,
path::Path,
ptr, slice,
sync::Arc,
};
use libc; use libc;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -6,12 +17,13 @@ use parking_lot::Mutex;
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::{ platform_impl::{
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle}, x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode,
}, },
window::{CursorIcon, Icon, WindowAttributes}, window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
}; };
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
@ -36,18 +48,22 @@ pub struct SharedState {
pub guessed_dpi: Option<f64>, pub guessed_dpi: Option<f64>,
pub last_monitor: Option<X11MonitorHandle>, pub last_monitor: Option<X11MonitorHandle>,
pub dpi_adjusted: Option<(f64, f64)>, pub dpi_adjusted: Option<(f64, f64)>,
pub fullscreen: Option<RootMonitorHandle>, pub fullscreen: Option<Fullscreen>,
// Used to restore position after exiting fullscreen. // Used to restore position after exiting fullscreen
pub restore_position: Option<(i32, i32)>, pub restore_position: Option<(i32, i32)>,
// Used to restore video mode after exiting fullscreen
pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>, pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<LogicalSize>, pub min_inner_size: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>, pub max_inner_size: Option<LogicalSize>,
pub is_visible: bool,
} }
impl SharedState { impl SharedState {
fn new(dpi_factor: f64) -> Mutex<Self> { fn new(dpi_factor: f64, is_visible: bool) -> Mutex<Self> {
let mut shared_state = SharedState::default(); let mut shared_state = SharedState::default();
shared_state.guessed_dpi = Some(dpi_factor); shared_state.guessed_dpi = Some(dpi_factor);
shared_state.is_visible = is_visible;
Mutex::new(shared_state) Mutex::new(shared_state)
} }
} }
@ -103,7 +119,7 @@ impl UnownedWindow {
.unwrap_or(1.0) .unwrap_or(1.0)
}) })
} else { } else {
return Err(os_error!(OsError::XMisc("No monitors were detected."))); 1.0
}; };
info!("Guessed window DPI factor: {}", dpi_factor); info!("Guessed window DPI factor: {}", dpi_factor);
@ -216,7 +232,7 @@ impl UnownedWindow {
cursor_grabbed: Mutex::new(false), cursor_grabbed: Mutex::new(false),
cursor_visible: Mutex::new(true), cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()), ime_sender: Mutex::new(event_loop.ime_sender.clone()),
shared_state: SharedState::new(dpi_factor), shared_state: SharedState::new(dpi_factor, window_attrs.visible),
pending_redraws: event_loop.pending_redraws.clone(), pending_redraws: event_loop.pending_redraws.clone(),
}; };
@ -282,9 +298,7 @@ impl UnownedWindow {
window.set_pid().map(|flusher| flusher.queue()); window.set_pid().map(|flusher| flusher.queue());
if pl_attribs.x11_window_type != Default::default() { window.set_window_types(pl_attribs.x11_window_types).queue();
window.set_window_type(pl_attribs.x11_window_type).queue();
}
if let Some(variant) = pl_attribs.gtk_theme_variant { if let Some(variant) = pl_attribs.gtk_theme_variant {
window.set_gtk_theme_variant(variant).queue(); window.set_gtk_theme_variant(variant).queue();
@ -341,6 +355,8 @@ impl UnownedWindow {
unsafe { unsafe {
(xconn.xlib.XMapRaised)(xconn.display, window.xwindow); (xconn.xlib.XMapRaised)(xconn.display, window.xwindow);
} //.queue(); } //.queue();
window.wait_for_visibility_notify();
} }
// Attempt to make keyboard input repeat detectable // Attempt to make keyboard input repeat detectable
@ -398,6 +414,7 @@ impl UnownedWindow {
if window_attrs.fullscreen.is_some() { if window_attrs.fullscreen.is_some() {
window window
.set_fullscreen_inner(window_attrs.fullscreen.clone()) .set_fullscreen_inner(window_attrs.fullscreen.clone())
.unwrap()
.queue(); .queue();
} }
if window_attrs.always_on_top { if window_attrs.always_on_top {
@ -405,27 +422,6 @@ impl UnownedWindow {
.set_always_on_top_inner(window_attrs.always_on_top) .set_always_on_top_inner(window_attrs.always_on_top)
.queue(); .queue();
} }
if window_attrs.visible {
unsafe {
// XSetInputFocus generates an error if the window is not visible, so we wait
// until we receive VisibilityNotify.
let mut event = mem::uninitialized();
(xconn.xlib.XIfEvent)(
// This will flush the request buffer IF it blocks.
xconn.display,
&mut event as *mut ffi::XEvent,
Some(visibility_predicate),
window.xwindow as _,
);
(xconn.xlib.XSetInputFocus)(
xconn.display,
window.xwindow,
ffi::RevertToParent,
ffi::CurrentTime,
);
}
}
} }
// We never want to give the user a broken window, since by then, it's too late to handle. // We never want to give the user a broken window, since by then, it's too late to handle.
@ -449,19 +445,22 @@ impl UnownedWindow {
let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") }; let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") };
let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") }; let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") };
unsafe { unsafe {
let (hostname, hostname_length) = { // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is // the limit defined by OpenBSD.
// the limit defined by OpenBSD. const MAXHOSTNAMELEN: usize = 256;
const MAXHOSTNAMELEN: usize = 256; // `assume_init` is safe here because the array consists of `MaybeUninit` values,
let mut hostname: [c_char; MAXHOSTNAMELEN] = mem::uninitialized(); // which do not require initialization.
let status = libc::gethostname(hostname.as_mut_ptr(), hostname.len()); let mut buffer: [MaybeUninit<c_char>; MAXHOSTNAMELEN] =
if status != 0 { MaybeUninit::uninit().assume_init();
return None; let status = libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len());
} if status != 0 {
hostname[MAXHOSTNAMELEN - 1] = '\0' as c_char; // a little extra safety return None;
let hostname_length = libc::strlen(hostname.as_ptr()); }
(hostname, hostname_length as usize) ptr::write(buffer[MAXHOSTNAMELEN - 1].as_mut_ptr() as *mut u8, b'\0'); // a little extra safety
}; let hostname_length = libc::strlen(buffer.as_ptr() as *const c_char);
let hostname = slice::from_raw_parts(buffer.as_ptr() as *const c_char, hostname_length);
self.xconn self.xconn
.change_property( .change_property(
self.xwindow, self.xwindow,
@ -482,15 +481,19 @@ impl UnownedWindow {
} }
} }
fn set_window_type(&self, window_type: util::WindowType) -> util::Flusher<'_> { fn set_window_types(&self, window_types: Vec<util::WindowType>) -> util::Flusher<'_> {
let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") }; let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") };
let window_type_atom = window_type.as_atom(&self.xconn); let atoms: Vec<_> = window_types
.iter()
.map(|t| t.as_atom(&self.xconn))
.collect();
self.xconn.change_property( self.xconn.change_property(
self.xwindow, self.xwindow,
hint_atom, hint_atom,
ffi::XA_ATOM, ffi::XA_ATOM,
util::PropMode::Replace, util::PropMode::Replace,
&[window_type_atom], &atoms,
) )
} }
@ -548,44 +551,151 @@ impl UnownedWindow {
fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> { fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> {
let fullscreen_atom = let fullscreen_atom =
unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") }; unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") };
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)) let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0));
if fullscreen {
// Ensure that the fullscreen window receives input focus to prevent
// locking up the user's display.
unsafe {
(self.xconn.xlib.XSetInputFocus)(
self.xconn.display,
self.xwindow,
ffi::RevertToParent,
ffi::CurrentTime,
);
}
}
flusher
} }
fn set_fullscreen_inner(&self, monitor: Option<RootMonitorHandle>) -> util::Flusher<'_> { fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
match monitor { let mut shared_state_lock = self.shared_state.lock();
if !shared_state_lock.is_visible {
// Setting fullscreen on a window that is not visible will generate an error.
return None;
}
let old_fullscreen = shared_state_lock.fullscreen.clone();
if old_fullscreen == fullscreen {
return None;
}
shared_state_lock.fullscreen = fullscreen.clone();
match (&old_fullscreen, &fullscreen) {
// Store the desktop video mode before entering exclusive
// fullscreen, so we can restore it upon exit, as XRandR does not
// provide a mechanism to set this per app-session or restore this
// to the desktop video mode as macOS and Windows do
(
&None,
&Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
})),
)
| (
&Some(Fullscreen::Borderless(_)),
&Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
})),
) => {
let monitor = video_mode.monitor.as_ref().unwrap();
shared_state_lock.desktop_video_mode =
Some((monitor.id, self.xconn.get_crtc_mode(monitor.id)));
}
// Restore desktop video mode upon exiting exclusive fullscreen
(&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
self.xconn
.set_crtc_config(monitor_id, mode_id)
.expect("failed to restore desktop video mode");
}
_ => (),
}
drop(shared_state_lock);
match fullscreen {
None => { None => {
let flusher = self.set_fullscreen_hint(false); let flusher = self.set_fullscreen_hint(false);
if let Some(position) = self.shared_state.lock().restore_position.take() { let mut shared_state_lock = self.shared_state.lock();
if let Some(position) = shared_state_lock.restore_position.take() {
self.set_position_inner(position.0, position.1).queue(); self.set_position_inner(position.0, position.1).queue();
} }
flusher Some(flusher)
} }
Some(RootMonitorHandle { Some(fullscreen) => {
inner: PlatformMonitorHandle::X(monitor), let (video_mode, monitor) = match fullscreen {
}) => { Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
}) => (Some(video_mode), video_mode.monitor.as_ref().unwrap()),
Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::X(ref monitor),
}) => (None, monitor),
_ => unreachable!(),
};
// Don't set fullscreen on an invalid dummy monitor handle
if monitor.id == 0 {
return None;
}
if let Some(video_mode) = video_mode {
// FIXME: this is actually not correct if we're setting the
// video mode to a resolution higher than the current
// desktop resolution, because XRandR does not automatically
// reposition the monitors to the right and below this
// monitor.
//
// What ends up happening is we will get the fullscreen
// window showing up on those monitors as well, because
// their virtual position now overlaps with the monitor that
// we just made larger..
//
// It'd be quite a bit of work to handle this correctly (and
// nobody else seems to bother doing this correctly either),
// so we're just leaving this broken. Fixing this would
// involve storing all CRTCs upon entering fullscreen,
// restoring them upon exit, and after entering fullscreen,
// repositioning displays to the right and below this
// display. I think there would still be edge cases that are
// difficult or impossible to handle correctly, e.g. what if
// a new monitor was plugged in while in fullscreen?
//
// I think we might just want to disallow setting the video
// mode higher than the current desktop video mode (I'm sure
// this will make someone unhappy, but it's very unusual for
// games to want to do this anyway).
self.xconn
.set_crtc_config(monitor.id, video_mode.native_mode)
.expect("failed to set video mode");
}
let window_position = self.outer_position_physical(); let window_position = self.outer_position_physical();
self.shared_state.lock().restore_position = Some(window_position); self.shared_state.lock().restore_position = Some(window_position);
let monitor_origin: (i32, i32) = monitor.position().into(); let monitor_origin: (i32, i32) = monitor.position().into();
self.set_position_inner(monitor_origin.0, monitor_origin.1) self.set_position_inner(monitor_origin.0, monitor_origin.1)
.queue(); .queue();
self.set_fullscreen_hint(true) Some(self.set_fullscreen_hint(true))
} }
_ => unreachable!(),
} }
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
self.shared_state.lock().fullscreen.clone() self.shared_state.lock().fullscreen.clone()
} }
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.shared_state.lock().fullscreen = monitor.clone(); if let Some(flusher) = self.set_fullscreen_inner(fullscreen) {
self.set_fullscreen_inner(monitor) flusher
.flush() .sync()
.expect("Failed to change window fullscreen state"); .expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents(); self.invalidate_cached_frame_extents();
}
} }
fn get_rect(&self) -> util::AaRect { fn get_rect(&self) -> util::AaRect {
@ -599,11 +709,11 @@ impl UnownedWindow {
pub fn current_monitor(&self) -> X11MonitorHandle { pub fn current_monitor(&self) -> X11MonitorHandle {
let monitor = self.shared_state.lock().last_monitor.as_ref().cloned(); let monitor = self.shared_state.lock().last_monitor.as_ref().cloned();
monitor.unwrap_or_else(|| { monitor.unwrap_or_else(|| {
let monitor = self let monitor = self.xconn.get_monitor_for_window(Some(self.get_rect()));
.xconn // Avoid caching an invalid dummy monitor handle
.get_monitor_for_window(Some(self.get_rect())) if monitor.id != 0 {
.to_owned(); self.shared_state.lock().last_monitor = Some(monitor.clone());
self.shared_state.lock().last_monitor = Some(monitor.clone()); }
monitor monitor
}) })
} }
@ -738,12 +848,22 @@ impl UnownedWindow {
#[inline] #[inline]
pub fn set_visible(&self, visible: bool) { pub fn set_visible(&self, visible: bool) {
let is_visible = self.shared_state.lock().is_visible;
if visible == is_visible {
return;
}
match visible { match visible {
true => unsafe { true => unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
self.xconn self.xconn
.flush_requests() .flush_requests()
.expect("Failed to call XMapRaised"); .expect("Failed to call XMapRaised");
// Some X requests may generate an error if the window is not
// visible, so we must wait until the window becomes visible.
self.wait_for_visibility_notify();
}, },
false => unsafe { false => unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
@ -752,6 +872,21 @@ impl UnownedWindow {
.expect("Failed to call XUnmapWindow"); .expect("Failed to call XUnmapWindow");
}, },
} }
self.shared_state.lock().is_visible = visible;
}
fn wait_for_visibility_notify(&self) {
unsafe {
let mut event = MaybeUninit::uninit();
(self.xconn.xlib.XIfEvent)(
self.xconn.display,
event.as_mut_ptr(),
Some(visibility_predicate),
self.xwindow as _,
);
}
} }
fn update_cached_frame_extents(&self) { fn update_cached_frame_extents(&self) {
@ -1025,131 +1160,14 @@ impl UnownedWindow {
unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ } unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ }
} }
fn load_cursor(&self, name: &[u8]) -> ffi::Cursor {
unsafe {
(self.xconn.xcursor.XcursorLibraryLoadCursor)(
self.xconn.display,
name.as_ptr() as *const c_char,
)
}
}
fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor {
for name in names.iter() {
let xcursor = self.load_cursor(name);
if xcursor != 0 {
return xcursor;
}
}
0
}
fn get_cursor(&self, cursor: CursorIcon) -> ffi::Cursor {
let load = |name: &[u8]| self.load_cursor(name);
let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names);
// Try multiple names in some cases where the name
// differs on the desktop environments or themes.
//
// Try the better looking (or more suiting) names first.
match cursor {
CursorIcon::Alias => load(b"link\0"),
CursorIcon::Arrow => load(b"arrow\0"),
CursorIcon::Cell => load(b"plus\0"),
CursorIcon::Copy => load(b"copy\0"),
CursorIcon::Crosshair => load(b"crosshair\0"),
CursorIcon::Default => load(b"left_ptr\0"),
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
CursorIcon::Help => load(b"question_arrow\0"),
CursorIcon::Move => load(b"move\0"),
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
CursorIcon::Progress => load(b"left_ptr_watch\0"),
CursorIcon::AllScroll => load(b"all-scroll\0"),
CursorIcon::ContextMenu => load(b"context-menu\0"),
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
// Resize cursors
CursorIcon::EResize => load(b"right_side\0"),
CursorIcon::NResize => load(b"top_side\0"),
CursorIcon::NeResize => load(b"top_right_corner\0"),
CursorIcon::NwResize => load(b"top_left_corner\0"),
CursorIcon::SResize => load(b"bottom_side\0"),
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
CursorIcon::WResize => load(b"left_side\0"),
CursorIcon::EwResize => load(b"h_double_arrow\0"),
CursorIcon::NsResize => load(b"v_double_arrow\0"),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
CursorIcon::VerticalText => load(b"vertical-text\0"),
CursorIcon::Wait => load(b"watch\0"),
CursorIcon::ZoomIn => load(b"zoom-in\0"),
CursorIcon::ZoomOut => load(b"zoom-out\0"),
}
}
fn update_cursor(&self, cursor: ffi::Cursor) {
unsafe {
(self.xconn.xlib.XDefineCursor)(self.xconn.display, self.xwindow, cursor);
if cursor != 0 {
(self.xconn.xlib.XFreeCursor)(self.xconn.display, cursor);
}
self.xconn
.flush_requests()
.expect("Failed to set or free the cursor");
}
}
#[inline] #[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
*self.cursor.lock() = cursor; let old_cursor = replace(&mut *self.cursor.lock(), cursor);
if *self.cursor_visible.lock() { if cursor != old_cursor && *self.cursor_visible.lock() {
self.update_cursor(self.get_cursor(cursor)); self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
} }
} }
// TODO: This could maybe be cached. I don't think it's worth
// the complexity, since cursor changes are not so common,
// and this is just allocating a 1x1 pixmap...
fn create_empty_cursor(&self) -> Option<ffi::Cursor> {
let data = 0;
let pixmap = unsafe {
(self.xconn.xlib.XCreateBitmapFromData)(self.xconn.display, self.xwindow, &data, 1, 1)
};
if pixmap == 0 {
// Failed to allocate
return None;
}
let cursor = unsafe {
// We don't care about this color, since it only fills bytes
// in the pixmap which are not 0 in the mask.
let dummy_color: ffi::XColor = mem::uninitialized();
let cursor = (self.xconn.xlib.XCreatePixmapCursor)(
self.xconn.display,
pixmap,
pixmap,
&dummy_color as *const _ as *mut _,
&dummy_color as *const _ as *mut _,
0,
0,
);
(self.xconn.xlib.XFreePixmap)(self.xconn.display, pixmap);
cursor
};
Some(cursor)
}
#[inline] #[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed.lock(); let mut grabbed_lock = self.cursor_grabbed.lock();
@ -1219,14 +1237,13 @@ impl UnownedWindow {
return; return;
} }
let cursor = if visible { let cursor = if visible {
self.get_cursor(*self.cursor.lock()) Some(*self.cursor.lock())
} else { } else {
self.create_empty_cursor() None
.expect("Failed to create empty cursor")
}; };
*visible_lock = visible; *visible_lock = visible;
drop(visible_lock); drop(visible_lock);
self.update_cursor(cursor); self.xconn.set_cursor_icon(self.xwindow, cursor);
} }
#[inline] #[inline]
@ -1277,4 +1294,13 @@ impl UnownedWindow {
.unwrap() .unwrap()
.insert(WindowId(self.xwindow)); .insert(WindowId(self.xwindow));
} }
#[inline]
pub fn raw_window_handle(&self) -> X11Handle {
X11Handle {
window: self.xwindow,
display: self.xconn.display as _,
..X11Handle::empty()
}
}
} }

View file

@ -1,8 +1,10 @@
use std::{error::Error, fmt, os::raw::c_int, ptr}; use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr};
use libc; use libc;
use parking_lot::Mutex; use parking_lot::Mutex;
use crate::window::CursorIcon;
use super::ffi; use super::ffi;
/// A connection to an X server. /// A connection to an X server.
@ -19,6 +21,7 @@ pub struct XConnection {
pub display: *mut ffi::Display, pub display: *mut ffi::Display,
pub x11_fd: c_int, pub x11_fd: c_int,
pub latest_error: Mutex<Option<XError>>, pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
} }
unsafe impl Send for XConnection {} unsafe impl Send for XConnection {}
@ -64,6 +67,7 @@ impl XConnection {
display, display,
x11_fd: fd, x11_fd: fd,
latest_error: Mutex::new(None), latest_error: Mutex::new(None),
cursor_cache: Default::default(),
}) })
} }

View file

@ -3,6 +3,7 @@ use std::{
fmt::{self, Debug}, fmt::{self, Debug},
hint::unreachable_unchecked, hint::unreachable_unchecked,
mem, mem,
rc::Rc,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Mutex, MutexGuard, Mutex, MutexGuard,
@ -37,13 +38,13 @@ pub trait EventHandler: Debug {
fn handle_user_events(&mut self, control_flow: &mut ControlFlow); fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
} }
struct EventLoopHandler<F, T: 'static> { struct EventLoopHandler<T: 'static> {
callback: F, callback: Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
will_exit: bool, will_exit: bool,
window_target: RootWindowTarget<T>, window_target: Rc<RootWindowTarget<T>>,
} }
impl<F, T> Debug for EventLoopHandler<F, T> { impl<T> Debug for EventLoopHandler<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter formatter
.debug_struct("EventLoopHandler") .debug_struct("EventLoopHandler")
@ -52,11 +53,7 @@ impl<F, T> Debug for EventLoopHandler<F, T> {
} }
} }
impl<F, T> EventHandler for EventLoopHandler<F, T> impl<T> EventHandler for EventLoopHandler<T> {
where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) { fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.callback)(event.userify(), &self.window_target, control_flow); (self.callback)(event.userify(), &self.window_target, control_flow);
self.will_exit |= *control_flow == ControlFlow::Exit; self.will_exit |= *control_flow == ControlFlow::Exit;
@ -87,7 +84,6 @@ struct Handler {
start_time: Mutex<Option<Instant>>, start_time: Mutex<Option<Instant>>,
callback: Mutex<Option<Box<dyn EventHandler>>>, callback: Mutex<Option<Box<dyn EventHandler>>>,
pending_events: Mutex<VecDeque<Event<Never>>>, pending_events: Mutex<VecDeque<Event<Never>>>,
deferred_events: Mutex<VecDeque<Event<Never>>>,
pending_redraw: Mutex<Vec<WindowId>>, pending_redraw: Mutex<Vec<WindowId>>,
waker: Mutex<EventLoopWaker>, waker: Mutex<EventLoopWaker>,
} }
@ -100,10 +96,6 @@ impl Handler {
self.pending_events.lock().unwrap() self.pending_events.lock().unwrap()
} }
fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque<Event<Never>>> {
self.deferred_events.lock().unwrap()
}
fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> { fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
self.pending_redraw.lock().unwrap() self.pending_redraw.lock().unwrap()
} }
@ -148,10 +140,6 @@ impl Handler {
mem::replace(&mut *self.events(), Default::default()) mem::replace(&mut *self.events(), Default::default())
} }
fn take_deferred(&self) -> VecDeque<Event<Never>> {
mem::replace(&mut *self.deferred(), Default::default())
}
fn should_redraw(&self) -> Vec<WindowId> { fn should_redraw(&self) -> Vec<WindowId> {
mem::replace(&mut *self.redraw(), Default::default()) mem::replace(&mut *self.redraw(), Default::default())
} }
@ -180,13 +168,20 @@ impl Handler {
pub enum AppState {} pub enum AppState {}
impl AppState { impl AppState {
pub fn set_callback<F, T>(callback: F, window_target: RootWindowTarget<T>) // This function extends lifetime of `callback` to 'static as its side effect
pub unsafe fn set_callback<F, T>(callback: F, window_target: Rc<RootWindowTarget<T>>)
where where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow), F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
T: 'static,
{ {
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback, // This transmute is always safe, in case it was reached through `run`, since our
// lifetime will be already 'static. In other cases caller should ensure that all data
// they passed to callback will actually outlive it, some apps just can't move
// everything to event loop, so this is something that they should care about.
callback: mem::transmute::<
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
>(Box::new(callback)),
will_exit: false, will_exit: false,
window_target, window_target,
})); }));
@ -196,6 +191,7 @@ impl AppState {
HANDLER.set_in_callback(true); HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(Event::LoopDestroyed); HANDLER.handle_nonuser_event(Event::LoopDestroyed);
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
HANDLER.callback.lock().unwrap().take();
} }
pub fn launched() { pub fn launched() {
@ -259,20 +255,6 @@ impl AppState {
HANDLER.events().append(&mut events); HANDLER.events().append(&mut events);
} }
pub fn send_event_immediately(event: Event<Never>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Event sent from different thread: {:#?}", event);
}
HANDLER.deferred().push_back(event);
if !HANDLER.get_in_callback() {
HANDLER.set_in_callback(true);
for event in HANDLER.take_deferred() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.set_in_callback(false);
}
}
pub fn cleared() { pub fn cleared() {
if !HANDLER.is_ready() { if !HANDLER.is_ready() {
return; return;
@ -298,7 +280,7 @@ impl AppState {
} }
HANDLER.update_start_time(); HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() { match HANDLER.get_old_and_new_control_flow() {
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(), (ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (),
(old, new) if old == new => (), (old, new) if old == new => (),
(_, ControlFlow::Wait) => HANDLER.waker().stop(), (_, ControlFlow::Wait) => HANDLER.waker().stop(),
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),

View file

@ -1,5 +1,6 @@
use std::{ use std::{
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, sync::mpsc, collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, rc::Rc,
sync::mpsc,
}; };
use cocoa::{ use cocoa::{
@ -34,7 +35,7 @@ impl<T> Default for EventLoopWindowTarget<T> {
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
window_target: RootWindowTarget<T>, window_target: Rc<RootWindowTarget<T>>,
_delegate: IdRef, _delegate: IdRef,
} }
@ -59,10 +60,10 @@ impl<T> EventLoop<T> {
}; };
setup_control_flow_observers(); setup_control_flow_observers();
EventLoop { EventLoop {
window_target: RootWindowTarget { window_target: Rc::new(RootWindowTarget {
p: Default::default(), p: Default::default(),
_marker: PhantomData, _marker: PhantomData,
}, }),
_delegate: delegate, _delegate: delegate,
} }
} }
@ -81,46 +82,50 @@ impl<T> EventLoop<T> {
&self.window_target &self.window_target
} }
pub fn run<F>(self, callback: F) -> ! pub fn run<F>(mut self, callback: F) -> !
where where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow), F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
self.run_return(callback);
process::exit(0);
}
pub fn run_return<F>(&mut self, callback: F)
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{ {
unsafe { unsafe {
let _pool = NSAutoreleasePool::new(nil); let _pool = NSAutoreleasePool::new(nil);
let app = NSApp(); let app = NSApp();
assert_ne!(app, nil); assert_ne!(app, nil);
AppState::set_callback(callback, self.window_target); AppState::set_callback(callback, Rc::clone(&self.window_target));
let _: () = msg_send![app, run]; let _: () = msg_send![app, run];
AppState::exit(); AppState::exit();
process::exit(0)
} }
} }
pub fn run_return<F>(&mut self, _callback: F)
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unimplemented!();
}
pub fn create_proxy(&self) -> Proxy<T> { pub fn create_proxy(&self) -> Proxy<T> {
Proxy::new(self.window_target.p.sender.clone()) Proxy::new(self.window_target.p.sender.clone())
} }
} }
#[derive(Clone)]
pub struct Proxy<T> { pub struct Proxy<T> {
sender: mpsc::Sender<T>, sender: mpsc::Sender<T>,
source: CFRunLoopSourceRef, source: CFRunLoopSourceRef,
} }
unsafe impl<T> Send for Proxy<T> {} unsafe impl<T: Send> Send for Proxy<T> {}
unsafe impl<T> Sync for Proxy<T> {}
impl<T> Clone for Proxy<T> {
fn clone(&self) -> Self {
Proxy::new(self.sender.clone())
}
}
impl<T> Proxy<T> { impl<T> Proxy<T> {
fn new(sender: mpsc::Sender<T>) -> Self { fn new(sender: mpsc::Sender<T>) -> Self {
unsafe { unsafe {
// just wakeup the eventloop // just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and // adding a Source to the main CFRunLoop lets us wake it up and

View file

@ -6,6 +6,13 @@ use cocoa::{
base::id, base::id,
foundation::{NSInteger, NSUInteger}, foundation::{NSInteger, NSUInteger},
}; };
use core_foundation::{
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
};
use core_graphics::{
base::CGError,
display::{CGDirectDisplayID, CGDisplayConfigRef},
};
use objc; use objc;
pub const NSNotFound: NSInteger = NSInteger::max_value(); pub const NSNotFound: NSInteger = NSInteger::max_value();
@ -108,3 +115,95 @@ pub enum NSWindowLevel {
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
} }
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
pub type CGDisplayFadeReservationToken = u32;
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
pub type Boolean = u8;
pub const FALSE: Boolean = 0;
pub const TRUE: Boolean = 1;
pub const kCGErrorSuccess: i32 = 0;
pub const kCGErrorFailure: i32 = 1000;
pub const kCGErrorIllegalArgument: i32 = 1001;
pub const kCGErrorInvalidConnection: i32 = 1002;
pub const kCGErrorInvalidContext: i32 = 1003;
pub const kCGErrorCannotComplete: i32 = 1004;
pub const kCGErrorNotImplemented: i32 = 1006;
pub const kCGErrorRangeCheck: i32 = 1007;
pub const kCGErrorTypeCheck: i32 = 1008;
pub const kCGErrorInvalidOperation: i32 = 1010;
pub const kCGErrorNoneAvailable: i32 = 1011;
pub const IO1BitIndexedPixels: &str = "P";
pub const IO2BitIndexedPixels: &str = "PP";
pub const IO4BitIndexedPixels: &str = "PPPP";
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut libc::c_void;
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
pub fn CGConfigureDisplayFadeEffect(
config: CGDisplayConfigRef,
fadeOutSeconds: CGDisplayFadeInterval,
fadeInSeconds: CGDisplayFadeInterval,
fadeRed: f32,
fadeGreen: f32,
fadeBlue: f32,
) -> CGError;
pub fn CGAcquireDisplayFadeReservation(
seconds: CGDisplayReservationInterval,
token: *mut CGDisplayFadeReservationToken,
) -> CGError;
pub fn CGDisplayFade(
token: CGDisplayFadeReservationToken,
duration: CGDisplayFadeInterval,
startBlend: CGDisplayBlendFraction,
endBlend: CGDisplayBlendFraction,
redBlend: f32,
greenBlend: f32,
blueBlend: f32,
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,
mode: CGDisplayModeRef,
options: CFDictionaryRef,
) -> CGError;
pub fn CGDisplayCopyAllDisplayModes(
display: CGDirectDisplayID,
options: CFDictionaryRef,
) -> CFArrayRef;
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
}

View file

@ -17,7 +17,7 @@ use std::{fmt, ops::Deref, sync::Arc};
pub use self::{ pub use self::{
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
}; };
use crate::{ use crate::{

View file

@ -1,25 +1,119 @@
use std::{collections::VecDeque, fmt}; use std::{collections::VecDeque, fmt};
use super::ffi;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::util::IdRef,
};
use cocoa::{ use cocoa::{
appkit::NSScreen, appkit::NSScreen,
base::{id, nil}, base::{id, nil},
foundation::{NSString, NSUInteger}, foundation::{NSString, NSUInteger},
}; };
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayMode}; use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
base::{CFRelease, TCFType},
string::CFString,
};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use core_video_sys::{ use core_video_sys::{
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay, kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease, CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
}; };
use crate::{ #[derive(Derivative)]
dpi::{PhysicalPosition, PhysicalSize}, #[derivative(Debug, Clone, PartialEq, Hash)]
monitor::VideoMode, pub struct VideoMode {
platform_impl::platform::util::IdRef, pub(crate) size: (u32, u32),
}; pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
#[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
pub(crate) native_mode: NativeDisplayMode,
}
#[derive(Clone, PartialEq)] pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
ffi::CGDisplayModeRelease(self.0);
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
ffi::CGDisplayModeRetain(self.0);
}
NativeDisplayMode(self.0)
}
}
impl VideoMode {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID); pub struct MonitorHandle(CGDirectDisplayID);
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
}
}
pub fn available_monitors() -> VecDeque<MonitorHandle> { pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() { if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len()); let mut monitors = VecDeque::with_capacity(displays.len());
@ -101,7 +195,7 @@ impl MonitorHandle {
unsafe { NSScreen::backingScaleFactor(screen) as f64 } unsafe { NSScreen::backingScaleFactor(screen) as f64 }
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let cv_refresh_rate = unsafe { let cv_refresh_rate = unsafe {
let mut display_link = std::ptr::null_mut(); let mut display_link = std::ptr::null_mut();
assert_eq!( assert_eq!(
@ -117,11 +211,27 @@ impl MonitorHandle {
time.timeScale as i64 / time.timeValue time.timeScale as i64 / time.timeValue
}; };
CGDisplayMode::all_display_modes(self.0, std::ptr::null()) let monitor = self.clone();
.expect("failed to obtain list of display modes")
.into_iter() unsafe {
.map(move |mode| { let modes = {
let cg_refresh_rate = mode.refresh_rate().round() as i64; let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.into_iter()
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that // CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT // isn't a CRT
@ -131,34 +241,55 @@ impl MonitorHandle {
cv_refresh_rate cv_refresh_rate
}; };
VideoMode { let pixel_encoding =
size: (mode.width() as u32, mode.height() as u32), CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode))
.to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
16
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
30
} else {
unimplemented!()
};
let video_mode = VideoMode {
size: (
ffi::CGDisplayModeGetPixelWidth(mode) as u32,
ffi::CGDisplayModeGetPixelHeight(mode) as u32,
),
refresh_rate: refresh_rate as u16, refresh_rate: refresh_rate as u16,
bit_depth: mode.bit_depth() as u16, bit_depth,
} monitor: monitor.clone(),
native_mode: NativeDisplayMode(mode),
};
RootVideoMode { video_mode }
}) })
}
} }
pub(crate) fn ns_screen(&self) -> Option<id> { pub(crate) fn ns_screen(&self) -> Option<id> {
unsafe { unsafe {
let native_id = self.native_identifier(); let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
let screens = NSScreen::screens(nil); let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count]; let count: NSUInteger = msg_send![screens, count];
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let mut matching_screen: Option<id> = None;
for i in 0..count { for i in 0..count {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger]; let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
let device_description = NSScreen::deviceDescription(screen); let device_description = NSScreen::deviceDescription(screen);
let value: id = msg_send![device_description, objectForKey:*key]; let value: id = msg_send![device_description, objectForKey:*key];
if value != nil { if value != nil {
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue]; let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
if screen_number as u32 == native_id { let other_uuid =
matching_screen = Some(screen); ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
break; if uuid == other_uuid {
return Some(screen);
} }
} }
} }
matching_screen None
} }
} }
} }

View file

@ -1,10 +1,9 @@
use std::{self, os::raw::*, ptr, time::Instant}; use std::{self, os::raw::*, ptr, time::Instant};
use crate::platform_impl::platform::app_state::AppState; use crate::platform_impl::platform::{app_state::AppState, ffi};
#[link(name = "CoreFoundation", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")]
extern "C" { extern "C" {
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode; pub static kCFRunLoopCommonModes: CFRunLoopMode;
pub fn CFRunLoopGetMain() -> CFRunLoopRef; pub fn CFRunLoopGetMain() -> CFRunLoopRef;
@ -13,7 +12,7 @@ extern "C" {
pub fn CFRunLoopObserverCreate( pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef, allocator: CFAllocatorRef,
activities: CFOptionFlags, activities: CFOptionFlags,
repeats: Boolean, repeats: ffi::Boolean,
order: CFIndex, order: CFIndex,
callout: CFRunLoopObserverCallBack, callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext, context: *mut CFRunLoopObserverContext,
@ -51,11 +50,6 @@ extern "C" {
pub fn CFRelease(cftype: *const c_void); pub fn CFRelease(cftype: *const c_void);
} }
pub type Boolean = u8;
#[allow(dead_code)]
const FALSE: Boolean = 0;
const TRUE: Boolean = 1;
pub enum CFAllocator {} pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator; pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {} pub enum CFRunLoop {}
@ -102,7 +96,7 @@ pub struct CFRunLoopSourceContext {
pub retain: extern "C" fn(*const c_void) -> *const c_void, pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void), pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, pub equal: extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode, pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
@ -162,12 +156,12 @@ impl RunLoop {
let observer = CFRunLoopObserverCreate( let observer = CFRunLoopObserverCreate(
ptr::null_mut(), ptr::null_mut(),
flags, flags,
TRUE, // Indicates we want this to run repeatedly ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run priority, // The lower the value, the sooner this will run
handler, handler,
ptr::null_mut(), ptr::null_mut(),
); );
CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode); CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
} }
} }
@ -204,9 +198,9 @@ impl Default for EventLoopWaker {
fn default() -> EventLoopWaker { fn default() -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe { unsafe {
// create a timer with a 1µs interval (1ns does not work) to mimic polling. // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// it is initially setup with a first fire time really far into the // It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediatley in did_finish_launching // future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate( let timer = CFRunLoopTimerCreate(
ptr::null_mut(), ptr::null_mut(),
std::f64::MAX, std::f64::MAX,

View file

@ -206,7 +206,10 @@ extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
} }
} }
} }
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
context.ns_window.setLevel_(0);
context.ns_window.toggleFullScreen_(nil); context.ns_window.toggleFullScreen_(nil);
} }
Box::from_raw(context_ptr); Box::from_raw(context_ptr);
@ -228,6 +231,21 @@ pub unsafe fn toggle_full_screen_async(
); );
} }
extern "C" fn restore_display_mode_callback(screen: *mut c_void) {
unsafe {
let screen = Box::from_raw(screen as *mut u32);
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess);
}
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
dispatch_async_f(
dispatch_get_main_queue(),
Box::into_raw(Box::new(ns_screen)) as *mut _,
restore_display_mode_callback,
);
}
struct SetMaximizedData { struct SetMaximizedData {
ns_window: id, ns_window: id,
is_zoomed: bool, is_zoomed: bool,

View file

@ -15,10 +15,12 @@ pub enum Cursor {
impl From<CursorIcon> for Cursor { impl From<CursorIcon> for Cursor {
fn from(cursor: CursorIcon) -> Self { fn from(cursor: CursorIcon) -> Self {
// See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc.
match cursor { match cursor {
CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"), CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"),
CursorIcon::Hand => Cursor::Native("pointingHandCursor"), CursorIcon::Hand => Cursor::Native("pointingHandCursor"),
CursorIcon::Grabbing | CursorIcon::Grab => Cursor::Native("closedHandCursor"), CursorIcon::Grab => Cursor::Native("openHandCursor"),
CursorIcon::Grabbing => Cursor::Native("closedHandCursor"),
CursorIcon::Text => Cursor::Native("IBeamCursor"), CursorIcon::Text => Cursor::Native("IBeamCursor"),
CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"), CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
CursorIcon::Copy => Cursor::Native("dragCopyCursor"), CursorIcon::Copy => Cursor::Native("dragCopyCursor"),

View file

@ -104,7 +104,7 @@ lazy_static! {
); );
decl.add_method( decl.add_method(
sel!(drawRect:), sel!(drawRect:),
draw_rect as extern "C" fn(&Object, Sel, id), draw_rect as extern "C" fn(&Object, Sel, NSRect),
); );
decl.add_method( decl.add_method(
sel!(acceptsFirstResponder), sel!(acceptsFirstResponder),
@ -280,7 +280,7 @@ extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) {
trace!("Completed `viewDidMoveToWindow`"); trace!("Completed `viewDidMoveToWindow`");
} }
extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: id) { extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
unsafe { unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState); let state = &mut *(state_ptr as *mut ViewState);

View file

@ -1,3 +1,4 @@
use raw_window_handle::{macos::MacOSHandle, RawWindowHandle};
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
f64, f64,
@ -8,6 +9,23 @@ use std::{
}, },
}; };
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
platform_impl::platform::{
app_state::AppState,
ffi,
monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef},
view::{self, new_view},
window_delegate::new_delegate,
OsError,
},
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
};
use cocoa::{ use cocoa::{
appkit::{ appkit::{
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
@ -17,30 +35,12 @@ use cocoa::{
base::{id, nil}, base::{id, nil},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
}; };
use core_graphics::display::CGDisplay; use core_graphics::display::{CGDisplay, CGDisplayMode};
use objc::{ use objc::{
declare::ClassDecl, declare::ClassDecl,
runtime::{Class, Object, Sel, BOOL, NO, YES}, runtime::{Class, Object, Sel, BOOL, NO, YES},
}; };
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
platform_impl::platform::{
app_state::AppState,
ffi,
monitor::{self, MonitorHandle},
util::{self, IdRef},
view::{self, new_view},
window_delegate::new_delegate,
OsError,
},
window::{CursorIcon, WindowAttributes, WindowId as RootWindowId},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub usize); pub struct Id(pub usize);
@ -66,6 +66,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub titlebar_buttons_hidden: bool, pub titlebar_buttons_hidden: bool,
pub fullsize_content_view: bool, pub fullsize_content_view: bool,
pub resize_increments: Option<LogicalSize>, pub resize_increments: Option<LogicalSize>,
pub disallow_hidpi: bool,
} }
fn create_app(activation_policy: ActivationPolicy) -> Option<id> { fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
@ -86,10 +87,15 @@ fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
} }
} }
unsafe fn create_view(ns_window: id) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> { unsafe fn create_view(
ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> {
let (ns_view, cursor) = new_view(ns_window); let (ns_view, cursor) = new_view(ns_window);
ns_view.non_nil().map(|ns_view| { ns_view.non_nil().map(|ns_view| {
ns_view.setWantsBestResolutionOpenGLSurface_(YES); if !pl_attribs.disallow_hidpi {
ns_view.setWantsBestResolutionOpenGLSurface_(YES);
}
// On Mojave, views automatically become layer-backed shortly after being added to // On Mojave, views automatically become layer-backed shortly after being added to
// a window. Changing the layer-backedness of a view breaks the association between // a window. Changing the layer-backedness of a view breaks the association between
@ -113,11 +119,14 @@ fn create_window(
unsafe { unsafe {
let pool = NSAutoreleasePool::new(nil); let pool = NSAutoreleasePool::new(nil);
let screen = match attrs.fullscreen { let screen = match attrs.fullscreen {
Some(ref monitor_id) => { Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }))
let monitor_screen = monitor_id.inner.ns_screen(); | Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: VideoMode { ref monitor, .. },
})) => {
let monitor_screen = monitor.ns_screen();
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
} }
_ => None, None => None,
}; };
let frame = match screen { let frame = match screen {
Some(screen) => appkit::NSScreen::frame(screen), Some(screen) => appkit::NSScreen::frame(screen),
@ -233,12 +242,15 @@ lazy_static! {
#[derive(Default)] #[derive(Default)]
pub struct SharedState { pub struct SharedState {
pub resizable: bool, pub resizable: bool,
pub fullscreen: Option<RootMonitorHandle>, pub fullscreen: Option<Fullscreen>,
pub maximized: bool, pub maximized: bool,
pub standard_frame: Option<NSRect>, pub standard_frame: Option<NSRect>,
is_simple_fullscreen: bool, is_simple_fullscreen: bool,
pub saved_style: Option<NSWindowStyleMask>, pub saved_style: Option<NSWindowStyleMask>,
/// Presentation options saved before entering `set_simple_fullscreen`, and
/// restored upon exiting it
save_presentation_opts: Option<NSApplicationPresentationOptions>, save_presentation_opts: Option<NSApplicationPresentationOptions>,
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
} }
impl SharedState { impl SharedState {
@ -301,10 +313,11 @@ impl UnownedWindow {
os_error!(OsError::CreationError("Couldn't create `NSWindow`")) os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
})?; })?;
let (ns_view, cursor) = unsafe { create_view(*ns_window) }.ok_or_else(|| { let (ns_view, cursor) =
unsafe { pool.drain() }; unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
os_error!(OsError::CreationError("Couldn't create `NSView`")) unsafe { pool.drain() };
})?; os_error!(OsError::CreationError("Couldn't create `NSView`"))
})?;
let input_context = unsafe { util::create_input_context(*ns_view) }; let input_context = unsafe { util::create_input_context(*ns_view) };
@ -355,16 +368,7 @@ impl UnownedWindow {
let delegate = new_delegate(&window, fullscreen.is_some()); let delegate = new_delegate(&window, fullscreen.is_some());
// Set fullscreen mode after we setup everything // Set fullscreen mode after we setup everything
if let Some(monitor) = fullscreen { window.set_fullscreen(fullscreen);
if monitor.inner != window.current_monitor().inner {
// To do this with native fullscreen, we probably need to
// warp the window... while we could use
// `enterFullScreenMode`, they're idiomatically different
// fullscreen modes, so we'd have to support both anyway.
unimplemented!();
}
window.set_fullscreen(Some(monitor));
}
// Setting the window as key has to happen *after* we set the fullscreen // Setting the window as key has to happen *after* we set the fullscreen
// state, since otherwise we'll briefly see the window at normal size // state, since otherwise we'll briefly see the window at normal size
@ -594,19 +598,22 @@ impl UnownedWindow {
} }
} }
/// This is called when the window is exiting fullscreen, whether by the
/// user clicking on the green fullscreen button or programmatically by
/// `toggleFullScreen:`
pub(crate) fn restore_state_from_fullscreen(&self) { pub(crate) fn restore_state_from_fullscreen(&self) {
let maximized = { trace!("Locked shared state in `restore_state_from_fullscreen`");
trace!("Locked shared state in `restore_state_from_fullscreen`"); let mut shared_state_lock = self.shared_state.lock().unwrap();
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen = None; shared_state_lock.fullscreen = None;
let mask = self.saved_style(&mut *shared_state_lock); let maximized = shared_state_lock.maximized;
let mask = self.saved_style(&mut *shared_state_lock);
self.set_style_mask_async(mask); drop(shared_state_lock);
shared_state_lock.maximized
};
trace!("Unocked shared state in `restore_state_from_fullscreen`"); trace!("Unocked shared state in `restore_state_from_fullscreen`");
self.set_style_mask_async(mask);
self.set_maximized(maximized); self.set_maximized(maximized);
} }
@ -627,44 +634,168 @@ impl UnownedWindow {
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
let shared_state_lock = self.shared_state.lock().unwrap(); let shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen.clone() shared_state_lock.fullscreen.clone()
} }
#[inline] #[inline]
/// TODO: Right now set_fullscreen do not work on switching monitors pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
/// in fullscreen mode trace!("Locked shared state in `set_fullscreen`");
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
let shared_state_lock = self.shared_state.lock().unwrap(); let shared_state_lock = self.shared_state.lock().unwrap();
if shared_state_lock.is_simple_fullscreen { if shared_state_lock.is_simple_fullscreen {
trace!("Unlocked shared state in `set_fullscreen`");
return; return;
} }
let old_fullscreen = shared_state_lock.fullscreen.clone();
let not_fullscreen = { if fullscreen == old_fullscreen {
trace!("Locked shared state in `set_fullscreen`");
let current = &shared_state_lock.fullscreen;
match (current, monitor) {
(&Some(ref a), Some(ref b)) if a.inner != b.inner => {
// Our best bet is probably to move to the origin of the
// target monitor.
unimplemented!()
}
(&None, None) | (&Some(_), Some(_)) => return,
_ => (),
}
trace!("Unlocked shared state in `set_fullscreen`"); trace!("Unlocked shared state in `set_fullscreen`");
current.is_none() return;
}; }
trace!("Unlocked shared state in `set_fullscreen`");
drop(shared_state_lock);
unsafe { // If the fullscreen is on a different monitor, we must move the window
util::toggle_full_screen_async( // to that monitor before we toggle fullscreen (as `toggleFullScreen`
*self.ns_window, // does not take a screen parameter, but uses the current screen)
*self.ns_view, if let Some(ref fullscreen) = fullscreen {
not_fullscreen, let new_screen = match fullscreen {
Arc::downgrade(&self.shared_state), Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor,
) Fullscreen::Exclusive(RootVideoMode {
}; video_mode: VideoMode { ref monitor, .. },
}) => monitor,
}
.ns_screen()
.unwrap();
unsafe {
let old_screen = NSWindow::screen(*self.ns_window);
if old_screen != new_screen {
let mut screen_frame: NSRect = msg_send![new_screen, frame];
// The coordinate system here has its origin at bottom-left
// and Y goes up
screen_frame.origin.y += screen_frame.size.height;
util::set_frame_top_left_point_async(*self.ns_window, screen_frame.origin);
}
}
}
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
// same thing as we're doing here (captures the display, sets the
// video mode, and hides the menu bar and dock), with the exception
// of that I couldn't figure out how to set the display mode with
// it. I think `enterFullScreenMode:withOptions:` is still using the
// older display mode API where display modes were of the type
// `CFDictionary`, but this has changed, so we can't obtain the
// correct parameter for this any longer. Apple's code samples for
// this function seem to just pass in "YES" for the display mode
// parameter, which is not consistent with the docs saying that it
// takes a `NSDictionary`..
let display_id = video_mode.monitor().inner.native_identifier();
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
unsafe {
// Fade to black (and wait for the fade to complete) to hide the
// flicker from capturing the display and switching display mode
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
== ffi::kCGErrorSuccess
{
ffi::CGDisplayFade(
fade_token,
0.3,
ffi::kCGDisplayBlendNormal,
ffi::kCGDisplayBlendSolidColor,
0.0,
0.0,
0.0,
ffi::TRUE,
);
}
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
}
unsafe {
let result = ffi::CGDisplaySetDisplayMode(
display_id,
video_mode.video_mode.native_mode.0,
std::ptr::null(),
);
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
// After the display has been configured, fade back in
// asynchronously
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
ffi::CGDisplayFade(
fade_token,
0.6,
ffi::kCGDisplayBlendSolidColor,
ffi::kCGDisplayBlendNormal,
0.0,
0.0,
0.0,
ffi::FALSE,
);
ffi::CGReleaseDisplayFadeReservation(fade_token);
}
}
}
trace!("Locked shared state in `set_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen = fullscreen.clone();
trace!("Unlocked shared state in `set_fullscreen`");
match (&old_fullscreen, &fullscreen) {
(&None, &Some(_)) => unsafe {
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
(&Some(Fullscreen::Borderless(_)), &None) => unsafe {
// State is restored by `window_did_exit_fullscreen`
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
(&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => unsafe {
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
// Rest of the state is restored by `window_did_exit_fullscreen`
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
(&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe {
// If we're already in fullscreen mode, calling
// `CGDisplayCapture` will place the shielding window on top of
// our window, which results in a black display and is not what
// we want. So, we must place our window on top of the shielding
// window. Unfortunately, this also makes our window be on top
// of the menu bar, and this looks broken, so we must make sure
// that the menu bar is disabled. This is done in the window
// delegate in `window:willUseFullScreenPresentationOptions:`.
msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1];
},
(
&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })),
&Some(Fullscreen::Borderless(_)),
) => unsafe {
util::restore_display_mode_async(video_mode.monitor().inner.native_identifier());
},
_ => (),
}
} }
#[inline] #[inline]
@ -764,6 +895,16 @@ impl UnownedWindow {
pub fn primary_monitor(&self) -> MonitorHandle { pub fn primary_monitor(&self) -> MonitorHandle {
monitor::primary_monitor() monitor::primary_monitor()
} }
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = MacOSHandle {
ns_window: *self.ns_window as *mut _,
ns_view: *self.ns_view as *mut _,
..MacOSHandle::empty()
};
RawWindowHandle::MacOS(handle)
}
} }
impl WindowExtMacOS for UnownedWindow { impl WindowExtMacOS for UnownedWindow {

View file

@ -5,9 +5,9 @@ use std::{
}; };
use cocoa::{ use cocoa::{
appkit::{self, NSView, NSWindow}, appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
base::{id, nil}, base::{id, nil},
foundation::NSAutoreleasePool, foundation::{NSAutoreleasePool, NSUInteger},
}; };
use objc::{ use objc::{
declare::ClassDecl, declare::ClassDecl,
@ -22,7 +22,7 @@ use crate::{
util::{self, IdRef}, util::{self, IdRef},
window::{get_window_id, UnownedWindow}, window::{get_window_id, UnownedWindow},
}, },
window::WindowId, window::{Fullscreen, WindowId},
}; };
pub struct WindowDelegateState { pub struct WindowDelegateState {
@ -84,11 +84,7 @@ impl WindowDelegateState {
pub fn emit_resize_event(&mut self) { pub fn emit_resize_event(&mut self) {
let rect = unsafe { NSView::frame(*self.ns_view) }; let rect = unsafe { NSView::frame(*self.ns_view) };
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let event = Event::WindowEvent { self.emit_event(WindowEvent::Resized(size));
window_id: WindowId(get_window_id(*self.ns_window)),
event: WindowEvent::Resized(size),
};
AppState::send_event_immediately(event);
} }
fn emit_move_event(&mut self) { fn emit_move_event(&mut self) {
@ -182,6 +178,11 @@ lazy_static! {
dragging_exited as extern "C" fn(&Object, Sel, id), dragging_exited as extern "C" fn(&Object, Sel, id),
); );
decl.add_method(
sel!(window:willUseFullScreenPresentationOptions:),
window_will_use_fullscreen_presentation_options
as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
);
decl.add_method( decl.add_method(
sel!(windowDidEnterFullScreen:), sel!(windowDidEnterFullScreen:),
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
@ -408,6 +409,26 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Completed `windowWillEnterFullscreen:`"); trace!("Completed `windowWillEnterFullscreen:`");
} }
extern "C" fn window_will_use_fullscreen_presentation_options(
_this: &Object,
_: Sel,
_: id,
_proposed_options: NSUInteger,
) -> NSUInteger {
// Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
// placed on top of the menu bar in exclusive fullscreen mode. This looks
// broken so we always disable the menu bar in exclusive fullscreen. We may
// still want to make this configurable for borderless fullscreen. Right now
// we don't, for consistency. If we do, it should be documented that the
// user-provided options are ignored in exclusive fullscreen.
(NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
.bits()
}
/// Invoked when entered fullscreen /// Invoked when entered fullscreen
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidEnterFullscreen:`"); trace!("Triggered `windowDidEnterFullscreen:`");
@ -415,8 +436,21 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
state.with_window(|window| { state.with_window(|window| {
let monitor = window.current_monitor(); let monitor = window.current_monitor();
trace!("Locked shared state in `window_did_enter_fullscreen`"); trace!("Locked shared state in `window_did_enter_fullscreen`");
window.shared_state.lock().unwrap().fullscreen = Some(monitor); let mut shared_state = window.shared_state.lock().unwrap();
trace!("Unlocked shared state in `window_will_enter_fullscreen`"); match shared_state.fullscreen {
// Exclusive mode sets the state in `set_fullscreen` as the user
// can't enter exclusive mode by other means (like the
// fullscreen button on the window decorations)
Some(Fullscreen::Exclusive(_)) => (),
// `window_did_enter_fullscreen` was triggered and we're already
// in fullscreen, so we must've reached here by `set_fullscreen`
// as it updates the state
Some(Fullscreen::Borderless(_)) => (),
// Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state!
None => shared_state.fullscreen = Some(Fullscreen::Borderless(monitor)),
}
trace!("Unlocked shared state in `window_did_enter_fullscreen`");
}); });
state.initial_fullscreen = false; state.initial_fullscreen = false;
}); });

View file

@ -21,9 +21,6 @@ mod platform;
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
#[path = "ios/mod.rs"] #[path = "ios/mod.rs"]
mod platform; mod platform;
#[cfg(target_os = "emscripten")]
#[path = "emscripten/mod.rs"]
mod platform;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[path = "web/mod.rs"] #[path = "web/mod.rs"]
mod platform; mod platform;
@ -38,7 +35,6 @@ mod platform;
not(target_os = "freebsd"), not(target_os = "freebsd"),
not(target_os = "netbsd"), not(target_os = "netbsd"),
not(target_os = "openbsd"), not(target_os = "openbsd"),
not(target_os = "emscripten"),
not(target_arch = "wasm32"), not(target_arch = "wasm32"),
))] ))]
compile_error!("The platform you're compiling for is not supported by winit"); compile_error!("The platform you're compiling for is not supported by winit");

View file

@ -1,6 +1,6 @@
#![allow(non_snake_case, unused_unsafe)] #![allow(non_snake_case, unused_unsafe)]
use std::{mem, os::raw::c_void, sync::Once}; use std::sync::Once;
use winapi::{ use winapi::{
shared::{ shared::{
@ -9,13 +9,12 @@ use winapi::{
winerror::S_OK, winerror::S_OK,
}, },
um::{ um::{
libloaderapi::{GetProcAddress, LoadLibraryA},
shellscalingapi::{ shellscalingapi::{
MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS, MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
PROCESS_PER_MONITOR_DPI_AWARE, PROCESS_PER_MONITOR_DPI_AWARE,
}, },
wingdi::{GetDeviceCaps, LOGPIXELSX}, wingdi::{GetDeviceCaps, LOGPIXELSX},
winnt::{HRESULT, LPCSTR}, winnt::HRESULT,
winuser::{self, MONITOR_DEFAULTTONEAREST}, winuser::{self, MONITOR_DEFAULTTONEAREST},
}, },
}; };
@ -35,33 +34,6 @@ type GetDpiForMonitor = unsafe extern "system" fn(
) -> HRESULT; ) -> HRESULT;
type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
};
}
lazy_static! { lazy_static! {
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> = static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> =
get_function!("user32.dll", GetDpiForWindow); get_function!("user32.dll", GetDpiForWindow);

View file

@ -1,3 +1,4 @@
#![allow(non_snake_case)]
//! An events loop on Win32 is a background thread. //! An events loop on Win32 is a background thread.
//! //!
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop. //! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
@ -38,14 +39,14 @@ use winapi::{
}, },
um::{ um::{
commctrl, libloaderapi, ole2, processthreadsapi, winbase, commctrl, libloaderapi, ole2, processthreadsapi, winbase,
winnt::{LONG, LPCSTR, SHORT}, winnt::{HANDLE, LONG, LPCSTR, SHORT},
winuser, winuser,
}, },
}; };
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize, PhysicalSize}, dpi::{LogicalPosition, LogicalSize, PhysicalSize},
event::{DeviceEvent, Event, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent}, event::{DeviceEvent, Event, Force, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::platform::{ platform_impl::platform::{
dpi::{ dpi::{
@ -62,6 +63,39 @@ use crate::{
window::WindowId as RootWindowId, window::WindowId as RootWindowId,
}; };
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
pointerId: UINT,
entriesCount: *mut UINT,
pointerCount: *mut UINT,
pointerInfo: *mut winuser::POINTER_INFO,
) -> BOOL;
type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: UINT) -> BOOL;
type GetPointerDeviceRects = unsafe extern "system" fn(
device: HANDLE,
pointerDeviceRect: *mut RECT,
displayRect: *mut RECT,
) -> BOOL;
type GetPointerTouchInfo =
unsafe extern "system" fn(pointerId: UINT, touchInfo: *mut winuser::POINTER_TOUCH_INFO) -> BOOL;
type GetPointerPenInfo =
unsafe extern "system" fn(pointId: UINT, penInfo: *mut winuser::POINTER_PEN_INFO) -> BOOL;
lazy_static! {
static ref GET_POINTER_FRAME_INFO_HISTORY: Option<GetPointerFrameInfoHistory> =
get_function!("user32.dll", GetPointerFrameInfoHistory);
static ref SKIP_POINTER_FRAME_MESSAGES: Option<SkipPointerFrameMessages> =
get_function!("user32.dll", SkipPointerFrameMessages);
static ref GET_POINTER_DEVICE_RECTS: Option<GetPointerDeviceRects> =
get_function!("user32.dll", GetPointerDeviceRects);
static ref GET_POINTER_TOUCH_INFO: Option<GetPointerTouchInfo> =
get_function!("user32.dll", GetPointerTouchInfo);
static ref GET_POINTER_PEN_INFO: Option<GetPointerPenInfo> =
get_function!("user32.dll", GetPointerPenInfo);
}
pub(crate) struct SubclassInput<T> { pub(crate) struct SubclassInput<T> {
pub window_state: Arc<Mutex<WindowState>>, pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: EventLoopRunnerShared<T>, pub event_loop_runner: EventLoopRunnerShared<T>,
@ -654,13 +688,21 @@ impl EventLoopThreadExecutor {
type ThreadExecFn = Box<Box<dyn FnMut()>>; type ThreadExecFn = Box<Box<dyn FnMut()>>;
#[derive(Clone)]
pub struct EventLoopProxy<T: 'static> { pub struct EventLoopProxy<T: 'static> {
target_window: HWND, target_window: HWND,
event_send: Sender<T>, event_send: Sender<T>,
} }
unsafe impl<T: Send + 'static> Send for EventLoopProxy<T> {} unsafe impl<T: Send + 'static> Send for EventLoopProxy<T> {}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
target_window: self.target_window,
event_send: self.event_send.clone(),
}
}
}
impl<T: 'static> EventLoopProxy<T> { impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
unsafe { unsafe {
@ -820,6 +862,13 @@ pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>)
assert_eq!(subclass_result, 1); assert_eq!(subclass_result, 1);
} }
fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
match pressure {
1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)),
_ => None,
}
}
/// Any window whose callback is configured to this function will have its events propagated /// Any window whose callback is configured to this function will have its events propagated
/// through the events loop of the thread the window was created in. /// through the events loop of the thread the window was created in.
// //
@ -982,11 +1031,34 @@ unsafe extern "system" fn public_window_callback<T>(
winuser::WM_CHAR => { winuser::WM_CHAR => {
use crate::event::WindowEvent::ReceivedCharacter; use crate::event::WindowEvent::ReceivedCharacter;
let chr: char = mem::transmute(wparam as u32); use std::char;
subclass_input.send_event(Event::WindowEvent { let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF;
window_id: RootWindowId(WindowId(window)), let is_low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF;
event: ReceivedCharacter(chr),
}); if is_high_surrogate {
subclass_input.window_state.lock().high_surrogate = Some(wparam as u16);
} else if is_low_surrogate {
let high_surrogate = subclass_input.window_state.lock().high_surrogate.take();
if let Some(high_surrogate) = high_surrogate {
let pair = [high_surrogate, wparam as u16];
if let Some(Ok(chr)) = char::decode_utf16(pair.iter().copied()).next() {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ReceivedCharacter(chr),
});
}
}
} else {
subclass_input.window_state.lock().high_surrogate = None;
if let Some(chr) = char::from_u32(wparam as u32) {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ReceivedCharacter(chr),
});
}
}
0 0
} }
@ -996,6 +1068,17 @@ unsafe extern "system" fn public_window_callback<T>(
// other unwanted default hotkeys as well. // other unwanted default hotkeys as well.
winuser::WM_SYSCHAR => 0, winuser::WM_SYSCHAR => 0,
winuser::WM_SYSCOMMAND => {
if wparam == winuser::SC_SCREENSAVE {
let window_state = subclass_input.window_state.lock();
if window_state.fullscreen.is_some() {
return 0;
}
}
winuser::DefWindowProcW(window, msg, wparam, lparam)
}
winuser::WM_MOUSEMOVE => { winuser::WM_MOUSEMOVE => {
use crate::event::WindowEvent::{CursorEntered, CursorMoved}; use crate::event::WindowEvent::{CursorEntered, CursorMoved};
let mouse_was_outside_window = { let mouse_was_outside_window = {
@ -1431,8 +1514,17 @@ unsafe extern "system" fn public_window_callback<T>(
{ {
let dpi_factor = hwnd_scale_factor(window); let dpi_factor = hwnd_scale_factor(window);
for input in &inputs { for input in &inputs {
let x = (input.x as f64) / 100f64; let mut location = POINT {
let y = (input.y as f64) / 100f64; x: input.x / 100,
y: input.y / 100,
};
if winuser::ScreenToClient(window, &mut location as *mut _) == 0 {
continue;
}
let x = location.x as f64 + (input.x % 100) as f64 / 100f64;
let y = location.y as f64 + (input.y % 100) as f64 / 100f64;
let location = LogicalPosition::from_physical((x, y), dpi_factor); let location = LogicalPosition::from_physical((x, y), dpi_factor);
subclass_input.send_event(Event::WindowEvent { subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)), window_id: RootWindowId(WindowId(window)),
@ -1447,6 +1539,7 @@ unsafe extern "system" fn public_window_callback<T>(
continue; continue;
}, },
location, location,
force: None, // WM_TOUCH doesn't support pressure information
id: input.dwID as u64, id: input.dwID as u64,
device_id: DEVICE_ID, device_id: DEVICE_ID,
}), }),
@ -1457,6 +1550,147 @@ unsafe extern "system" fn public_window_callback<T>(
0 0
} }
winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => {
if let (
Some(GetPointerFrameInfoHistory),
Some(SkipPointerFrameMessages),
Some(GetPointerDeviceRects),
) = (
*GET_POINTER_FRAME_INFO_HISTORY,
*SKIP_POINTER_FRAME_MESSAGES,
*GET_POINTER_DEVICE_RECTS,
) {
let pointer_id = LOWORD(wparam as DWORD) as UINT;
let mut entries_count = 0 as UINT;
let mut pointers_count = 0 as UINT;
if GetPointerFrameInfoHistory(
pointer_id,
&mut entries_count as *mut _,
&mut pointers_count as *mut _,
std::ptr::null_mut(),
) == 0
{
return 0;
}
let pointer_info_count = (entries_count * pointers_count) as usize;
let mut pointer_infos = Vec::with_capacity(pointer_info_count);
pointer_infos.set_len(pointer_info_count);
if GetPointerFrameInfoHistory(
pointer_id,
&mut entries_count as *mut _,
&mut pointers_count as *mut _,
pointer_infos.as_mut_ptr(),
) == 0
{
return 0;
}
let dpi_factor = hwnd_scale_factor(window);
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory
// The information retrieved appears in reverse chronological order, with the most recent entry in the first
// row of the returned array
for pointer_info in pointer_infos.iter().rev() {
let mut device_rect = mem::MaybeUninit::uninit();
let mut display_rect = mem::MaybeUninit::uninit();
if (GetPointerDeviceRects(
pointer_info.sourceDevice,
device_rect.as_mut_ptr(),
display_rect.as_mut_ptr(),
)) == 0
{
continue;
}
let device_rect = device_rect.assume_init();
let display_rect = display_rect.assume_init();
// For the most precise himetric to pixel conversion we calculate the ratio between the resolution
// of the display device (pixel) and the touch device (himetric).
let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64
/ (device_rect.right - device_rect.left) as f64;
let himetric_to_pixel_ratio_y = (display_rect.bottom - display_rect.top) as f64
/ (device_rect.bottom - device_rect.top) as f64;
// ptHimetricLocation's origin is 0,0 even on multi-monitor setups.
// On multi-monitor setups we need to translate the himetric location to the rect of the
// display device it's attached to.
let x = display_rect.left as f64
+ pointer_info.ptHimetricLocation.x as f64 * himetric_to_pixel_ratio_x;
let y = display_rect.top as f64
+ pointer_info.ptHimetricLocation.y as f64 * himetric_to_pixel_ratio_y;
let mut location = POINT {
x: x.floor() as i32,
y: y.floor() as i32,
};
if winuser::ScreenToClient(window, &mut location as *mut _) == 0 {
continue;
}
let force = match pointer_info.pointerType {
winuser::PT_TOUCH => {
let mut touch_info = mem::MaybeUninit::uninit();
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
match GetPointerTouchInfo(
pointer_info.pointerId,
touch_info.as_mut_ptr(),
) {
0 => None,
_ => normalize_pointer_pressure(
touch_info.assume_init().pressure,
),
}
})
}
winuser::PT_PEN => {
let mut pen_info = mem::MaybeUninit::uninit();
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
match GetPointerPenInfo(
pointer_info.pointerId,
pen_info.as_mut_ptr(),
) {
0 => None,
_ => {
normalize_pointer_pressure(pen_info.assume_init().pressure)
}
}
})
}
_ => None,
};
let x = location.x as f64 + x.fract();
let y = location.y as f64 + y.fract();
let location = LogicalPosition::from_physical((x, y), dpi_factor);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::Touch(Touch {
phase: if pointer_info.pointerFlags & winuser::POINTER_FLAG_DOWN != 0 {
TouchPhase::Started
} else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UP != 0 {
TouchPhase::Ended
} else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UPDATE != 0
{
TouchPhase::Moved
} else {
continue;
},
location,
force,
id: pointer_info.pointerId as u64,
device_id: DEVICE_ID,
}),
});
}
SkipPointerFrameMessages(pointer_id);
}
0
}
winuser::WM_SETFOCUS => { winuser::WM_SETFOCUS => {
use crate::event::WindowEvent::Focused; use crate::event::WindowEvent::Focused;
subclass_input.send_event(Event::WindowEvent { subclass_input.send_event(Event::WindowEvent {

View file

@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND};
pub use self::{ pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::Window, window::Window,
}; };
@ -67,6 +67,8 @@ impl WindowId {
} }
} }
#[macro_use]
mod util;
mod dpi; mod dpi;
mod drop_handler; mod drop_handler;
mod event; mod event;
@ -74,6 +76,5 @@ mod event_loop;
mod icon; mod icon;
mod monitor; mod monitor;
mod raw_input; mod raw_input;
mod util;
mod window; mod window;
mod window_state; mod window_state;

View file

@ -3,54 +3,64 @@ use winapi::{
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD}, minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
windef::{HDC, HMONITOR, HWND, LPRECT, POINT}, windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
}, },
um::{wingdi, winnt::LONG, winuser}, um::{wingdi, winuser},
}; };
use std::{ use std::{
collections::{HashSet, VecDeque}, collections::{BTreeSet, VecDeque},
io, mem, ptr, io, mem, ptr,
}; };
use super::{util, EventLoop}; use super::{util, EventLoop};
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{ platform_impl::platform::{
dpi::{dpi_to_scale_factor, get_monitor_dpi}, dpi::{dpi_to_scale_factor, get_monitor_dpi},
window::Window, window::Window,
}, },
}; };
/// Win32 implementation of the main `MonitorHandle` object.
#[derive(Derivative)] #[derive(Derivative)]
#[derivative(Debug, Clone)] #[derivative(Debug, Clone, Eq, PartialEq, Hash)]
pub struct MonitorHandle { pub struct VideoMode {
/// Monitor handle. pub(crate) size: (u32, u32),
hmonitor: HMonitor, pub(crate) bit_depth: u16,
#[derivative(Debug = "ignore")] pub(crate) refresh_rate: u16,
monitor_info: winuser::MONITORINFOEXW, pub(crate) monitor: MonitorHandle,
/// The system name of the monitor. #[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
monitor_name: String, pub(crate) native_video_mode: wingdi::DEVMODEW,
/// True if this is the primary monitor.
primary: bool,
/// The position of the monitor in pixels on the desktop.
///
/// A window that is positioned at these coordinates will overlap the monitor.
position: (i32, i32),
/// The current resolution in pixels on the monitor.
dimensions: (u32, u32),
/// DPI scale factor.
hidpi_factor: f64,
} }
impl VideoMode {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle(HMONITOR);
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually. // Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
// For more info see: // For more info see:
// https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396 // https://github.com/retep998/winapi-rs/issues/396
#[derive(Debug, Clone)]
struct HMonitor(HMONITOR);
unsafe impl Send for HMonitor {} unsafe impl Send for MonitorHandle {}
unsafe extern "system" fn monitor_enum_proc( unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR, hmonitor: HMONITOR,
@ -59,7 +69,7 @@ unsafe extern "system" fn monitor_enum_proc(
data: LPARAM, data: LPARAM,
) -> BOOL { ) -> BOOL {
let monitors = data as *mut VecDeque<MonitorHandle>; let monitors = data as *mut VecDeque<MonitorHandle>;
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor)); (*monitors).push_back(MonitorHandle::new(hmonitor));
TRUE // continue enumeration TRUE // continue enumeration
} }
@ -79,12 +89,12 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
pub fn primary_monitor() -> MonitorHandle { pub fn primary_monitor() -> MonitorHandle {
const ORIGIN: POINT = POINT { x: 0, y: 0 }; const ORIGIN: POINT = POINT { x: 0, y: 0 };
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) }; let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
MonitorHandle::from_hmonitor(hmonitor) MonitorHandle::new(hmonitor)
} }
pub fn current_monitor(hwnd: HWND) -> MonitorHandle { pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) }; let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
MonitorHandle::from_hmonitor(hmonitor) MonitorHandle::new(hmonitor)
} }
impl<T> EventLoop<T> { impl<T> EventLoop<T> {
@ -125,73 +135,69 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINF
} }
impl MonitorHandle { impl MonitorHandle {
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self { pub(crate) fn new(hmonitor: HMONITOR) -> Self {
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed"); MonitorHandle(hmonitor)
let place = monitor_info.rcMonitor;
let dimensions = (
(place.right - place.left) as u32,
(place.bottom - place.top) as u32,
);
MonitorHandle {
hmonitor: HMonitor(hmonitor),
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
position: (place.left as i32, place.top as i32),
dimensions,
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
monitor_info,
}
} }
pub(crate) fn contains_point(&self, point: &POINT) -> bool { pub(crate) fn contains_point(&self, point: &POINT) -> bool {
let left = self.position.0 as LONG; let monitor_info = get_monitor_info(self.0).unwrap();
let right = left + self.dimensions.0 as LONG; point.x >= monitor_info.rcMonitor.left
let top = self.position.1 as LONG; && point.x <= monitor_info.rcMonitor.right
let bottom = top + self.dimensions.1 as LONG; && point.y >= monitor_info.rcMonitor.top
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom && point.y <= monitor_info.rcMonitor.bottom
} }
#[inline] #[inline]
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
Some(self.monitor_name.clone()) let monitor_info = get_monitor_info(self.0).unwrap();
Some(util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()))
} }
#[inline] #[inline]
pub fn native_identifier(&self) -> String { pub fn native_identifier(&self) -> String {
self.monitor_name.clone() self.name().unwrap()
} }
#[inline] #[inline]
pub fn hmonitor(&self) -> HMONITOR { pub fn hmonitor(&self) -> HMONITOR {
self.hmonitor.0 self.0
} }
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize { pub fn size(&self) -> PhysicalSize {
self.dimensions.into() let monitor_info = get_monitor_info(self.0).unwrap();
PhysicalSize {
width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as f64,
height: (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as f64,
}
} }
#[inline] #[inline]
pub fn position(&self) -> PhysicalPosition { pub fn position(&self) -> PhysicalPosition {
self.position.into() let monitor_info = get_monitor_info(self.0).unwrap();
PhysicalPosition {
x: monitor_info.rcMonitor.left as f64,
y: monitor_info.rcMonitor.top as f64,
}
} }
#[inline] #[inline]
pub fn hidpi_factor(&self) -> f64 { pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
} }
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
// EnumDisplaySettingsExW can return duplicate values (or some of the // EnumDisplaySettingsExW can return duplicate values (or some of the
// fields are probably changing, but we aren't looking at those fields // fields are probably changing, but we aren't looking at those fields
// anyway), so we're using a HashSet deduplicate // anyway), so we're using a BTreeSet deduplicate
let mut modes = HashSet::new(); let mut modes = BTreeSet::new();
let mut i = 0; let mut i = 0;
loop { loop {
unsafe { unsafe {
let device_name = self.monitor_info.szDevice.as_ptr(); let monitor_info = get_monitor_info(self.0).unwrap();
let device_name = monitor_info.szDevice.as_ptr();
let mut mode: wingdi::DEVMODEW = mem::zeroed(); let mut mode: wingdi::DEVMODEW = mem::zeroed();
mode.dmSize = mem::size_of_val(&mode) as WORD; mode.dmSize = mem::size_of_val(&mode) as WORD;
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 { if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
@ -205,10 +211,14 @@ impl MonitorHandle {
| wingdi::DM_DISPLAYFREQUENCY; | wingdi::DM_DISPLAYFREQUENCY;
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS); assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
modes.insert(VideoMode { modes.insert(RootVideoMode {
size: (mode.dmPelsWidth, mode.dmPelsHeight), video_mode: VideoMode {
bit_depth: mode.dmBitsPerPel as u16, size: (mode.dmPelsWidth, mode.dmPelsHeight),
refresh_rate: mode.dmDisplayFrequency as u16, bit_depth: mode.dmBitsPerPel as u16,
refresh_rate: mode.dmDisplayFrequency as u16,
monitor: self.clone(),
native_video_mode: mode,
},
}); });
} }
} }

View file

@ -1,6 +1,7 @@
use std::{ use std::{
io, mem, io, mem,
ops::BitAnd, ops::BitAnd,
os::raw::c_void,
ptr, slice, ptr, slice,
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
}; };
@ -12,9 +13,44 @@ use winapi::{
minwindef::{BOOL, DWORD}, minwindef::{BOOL, DWORD},
windef::{HWND, POINT, RECT}, windef::{HWND, POINT, RECT},
}, },
um::{winbase::lstrlenW, winuser}, um::{
libloaderapi::{GetProcAddress, LoadLibraryA},
winbase::lstrlenW,
winnt::LPCSTR,
winuser,
},
}; };
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
crate::platform_impl::platform::util::get_function_impl(
concat!($lib, '\0'),
concat!(stringify!($func), '\0'),
)
.map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
};
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool pub fn has_flag<T>(bitset: T, flag: T) -> bool
where where
T: Copy + PartialEq + BitAnd<T, Output = T>, T: Copy + PartialEq + BitAnd<T, Output = T>,
@ -105,6 +141,16 @@ pub fn set_cursor_hidden(hidden: bool) {
} }
} }
pub fn get_cursor_clip() -> Result<RECT, io::Error> {
unsafe {
let mut rect: RECT = mem::zeroed();
win_to_err(|| winuser::GetClipCursor(&mut rect)).map(|_| rect)
}
}
/// Sets the cursor's clip rect.
///
/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event.
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> { pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
unsafe { unsafe {
let rect_ptr = rect let rect_ptr = rect
@ -115,6 +161,19 @@ pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
} }
} }
pub fn get_desktop_rect() -> RECT {
unsafe {
let left = winuser::GetSystemMetrics(winuser::SM_XVIRTUALSCREEN);
let top = winuser::GetSystemMetrics(winuser::SM_YVIRTUALSCREEN);
RECT {
left,
top,
right: left + winuser::GetSystemMetrics(winuser::SM_CXVIRTUALSCREEN),
bottom: top + winuser::GetSystemMetrics(winuser::SM_CYVIRTUALSCREEN),
}
}
}
pub fn is_focused(window: HWND) -> bool { pub fn is_focused(window: HWND) -> bool {
window == unsafe { winuser::GetActiveWindow() } window == unsafe { winuser::GetActiveWindow() }
} }

View file

@ -1,6 +1,7 @@
#![cfg(target_os = "windows")] #![cfg(target_os = "windows")]
use parking_lot::Mutex; use parking_lot::Mutex;
use raw_window_handle::{windows::WindowsHandle, RawWindowHandle};
use std::{ use std::{
cell::Cell, cell::Cell,
ffi::OsStr, ffi::OsStr,
@ -46,7 +47,7 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId, PlatformSpecificWindowBuilderAttributes, WindowId,
}, },
window::{CursorIcon, Icon, WindowAttributes}, window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
}; };
/// The Win32 implementation of the main `Window` object. /// The Win32 implementation of the main `Window` object.
@ -211,6 +212,15 @@ impl Window {
pub fn set_outer_position(&self, logical_position: LogicalPosition) { pub fn set_outer_position(&self, logical_position: LogicalPosition) {
let dpi_factor = self.hidpi_factor(); let dpi_factor = self.hidpi_factor();
let (x, y) = logical_position.to_physical(dpi_factor).into(); let (x, y) = logical_position.to_physical(dpi_factor).into();
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
self.set_position_physical(x, y); self.set_position_physical(x, y);
} }
@ -286,6 +296,15 @@ impl Window {
pub fn set_inner_size(&self, logical_size: LogicalSize) { pub fn set_inner_size(&self, logical_size: LogicalSize) {
let dpi_factor = self.hidpi_factor(); let dpi_factor = self.hidpi_factor();
let (width, height) = logical_size.to_physical(dpi_factor).into(); let (width, height) = logical_size.to_physical(dpi_factor).into();
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MAXIMIZED, false)
});
});
self.set_inner_size_physical(width, height); self.set_inner_size_physical(width, height);
} }
@ -327,7 +346,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::RESIZABLE, resizable) f.set(WindowFlags::RESIZABLE, resizable)
}); });
}); });
@ -339,6 +358,15 @@ impl Window {
self.window.0 self.window.0
} }
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = WindowsHandle {
hwnd: self.window.0 as *mut _,
..WindowsHandle::empty()
};
RawWindowHandle::Windows(handle)
}
#[inline] #[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window_state.lock().mouse.cursor = cursor; self.window_state.lock().mouse.cursor = cursor;
@ -421,80 +449,178 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MAXIMIZED, maximized) f.set(WindowFlags::MAXIMIZED, maximized)
}); });
}); });
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
let window_state = self.window_state.lock(); let window_state = self.window_state.lock();
window_state.fullscreen.clone() window_state.fullscreen.clone()
} }
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
unsafe { let window = self.window.clone();
let window = self.window.clone(); let window_state = Arc::clone(&self.window_state);
let window_state = Arc::clone(&self.window_state);
match &monitor { let mut window_state_lock = window_state.lock();
&Some(RootMonitorHandle { ref inner }) => { let old_fullscreen = window_state_lock.fullscreen.clone();
let (x, y): (i32, i32) = inner.position().into(); if window_state_lock.fullscreen == fullscreen {
let (width, height): (u32, u32) = inner.size().into(); return;
}
window_state_lock.fullscreen = fullscreen.clone();
drop(window_state_lock);
let mut monitor = monitor.clone(); self.thread_executor.execute_in_thread(move || {
self.thread_executor.execute_in_thread(move || { let mut window_state_lock = window_state.lock();
let mut window_state_lock = window_state.lock();
let client_rect = // Save window bounds before entering fullscreen
util::get_client_rect(window.0).expect("get client rect failed!"); match (&old_fullscreen, &fullscreen) {
window_state_lock.saved_window = Some(SavedWindow { (&None, &Some(_)) => {
client_rect, let client_rect = util::get_client_rect(window.0).unwrap();
dpi_factor: window_state_lock.dpi_factor, window_state_lock.saved_window = Some(SavedWindow {
}); client_rect,
dpi_factor: window_state_lock.dpi_factor,
window_state_lock.fullscreen = monitor.take();
WindowState::refresh_window_state(
window_state_lock,
window.0,
Some(RECT {
left: x,
top: y,
right: x + width as c_int,
bottom: y + height as c_int,
}),
);
mark_fullscreen(window.0, true);
}); });
} }
&None => { _ => (),
self.thread_executor.execute_in_thread(move || { }
let mut window_state_lock = window_state.lock();
window_state_lock.fullscreen = None;
if let Some(SavedWindow { // Change video mode if we're transitioning to or from exclusive
client_rect, // fullscreen
dpi_factor, match (&old_fullscreen, &fullscreen) {
}) = window_state_lock.saved_window (&None, &Some(Fullscreen::Exclusive(ref video_mode)))
{ | (
window_state_lock.dpi_factor = dpi_factor; &Some(Fullscreen::Borderless(_)),
window_state_lock.saved_window = None; &Some(Fullscreen::Exclusive(ref video_mode)),
)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) =>
{
let monitor = video_mode.monitor();
WindowState::refresh_window_state( let mut display_name = OsStr::new(&monitor.inner.native_identifier())
window_state_lock, .encode_wide()
.collect::<Vec<_>>();
// `encode_wide` does not add a null-terminator but
// `ChangeDisplaySettingsExW` requires a null-terminated
// string, so add it
display_name.push(0);
let mut native_video_mode = video_mode.video_mode.native_video_mode.clone();
let res = unsafe {
winuser::ChangeDisplaySettingsExW(
display_name.as_ptr(),
&mut native_video_mode,
std::ptr::null_mut(),
winuser::CDS_FULLSCREEN,
std::ptr::null_mut(),
)
};
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
}
(&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
let res = unsafe {
winuser::ChangeDisplaySettingsExW(
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
winuser::CDS_FULLSCREEN,
std::ptr::null_mut(),
)
};
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
}
_ => (),
}
unsafe {
// There are some scenarios where calling `ChangeDisplaySettingsExW` takes long
// enough to execute that the DWM thinks our program has frozen and takes over
// our program's window. When that happens, the `SetWindowPos` call below gets
// eaten and the window doesn't get set to the proper fullscreen position.
//
// Calling `PeekMessageW` here notifies Windows that our process is still running
// fine, taking control back from the DWM and ensuring that the `SetWindowPos` call
// below goes through.
let mut msg = mem::zeroed();
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0);
}
// Update window style
WindowState::set_window_flags(window_state_lock, window.0, |f| {
f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some())
});
// Update window bounds
match &fullscreen {
Some(fullscreen) => {
let monitor = match fullscreen {
Fullscreen::Exclusive(ref video_mode) => video_mode.monitor(),
Fullscreen::Borderless(ref monitor) => monitor.clone(),
};
let position: (i32, i32) = monitor.position().into();
let size: (u32, u32) = monitor.size().into();
unsafe {
winuser::SetWindowPos(
window.0,
ptr::null_mut(),
position.0,
position.1,
size.0 as i32,
size.1 as i32,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
);
winuser::UpdateWindow(window.0);
}
}
None => {
let mut window_state_lock = window_state.lock();
if let Some(SavedWindow {
client_rect,
dpi_factor,
}) = window_state_lock.saved_window.take()
{
window_state_lock.dpi_factor = dpi_factor;
drop(window_state_lock);
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
unsafe {
winuser::SetWindowPos(
window.0, window.0,
Some(client_rect), ptr::null_mut(),
client_rect.left,
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
); );
winuser::UpdateWindow(window.0);
} }
}
mark_fullscreen(window.0, false);
});
} }
} }
}
unsafe {
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
}
});
} }
#[inline] #[inline]
@ -503,8 +629,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { self.thread_executor.execute_in_thread(move || {
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!"); WindowState::set_window_flags(window_state.lock(), window.0, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| {
f.set(WindowFlags::DECORATIONS, decorations) f.set(WindowFlags::DECORATIONS, decorations)
}); });
}); });
@ -516,7 +641,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top) f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
}); });
}); });
@ -769,9 +894,7 @@ unsafe fn init<T: 'static>(
let window_state = { let window_state = {
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor); let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
let window_state = Arc::new(Mutex::new(window_state)); let window_state = Arc::new(Mutex::new(window_state));
WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| { WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
*f = window_flags
});
window_state window_state
}; };
@ -865,7 +988,7 @@ pub fn com_initialized() {
// is activated. If the window is not fullscreen, the Shell falls back to // is activated. If the window is not fullscreen, the Shell falls back to
// heuristics to determine how the window should be treated, which means // heuristics to determine how the window should be treated, which means
// that it could still consider the window as fullscreen. :( // that it could still consider the window as fullscreen. :(
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) { unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) {
com_initialized(); com_initialized();
TASKBAR_LIST.with(|task_bar_list_ptr| { TASKBAR_LIST.with(|task_bar_list_ptr| {

View file

@ -1,8 +1,7 @@
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
monitor::MonitorHandle,
platform_impl::platform::{event_loop, icon::WinIcon, util}, platform_impl::platform::{event_loop, icon::WinIcon, util},
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
}; };
use parking_lot::MutexGuard; use parking_lot::MutexGuard;
use std::{io, ptr}; use std::{io, ptr};
@ -29,10 +28,11 @@ pub struct WindowState {
pub saved_window: Option<SavedWindow>, pub saved_window: Option<SavedWindow>,
pub dpi_factor: f64, pub dpi_factor: f64,
pub fullscreen: Option<MonitorHandle>, pub fullscreen: Option<Fullscreen>,
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
/// times in `EventsCleared`. /// times in `EventsCleared`.
pub queued_out_of_band_redraw: bool, pub queued_out_of_band_redraw: bool,
pub high_surrogate: Option<u16>,
window_flags: WindowFlags, window_flags: WindowFlags,
} }
@ -84,6 +84,7 @@ bitflags! {
WindowFlags::RESIZABLE.bits | WindowFlags::RESIZABLE.bits |
WindowFlags::MAXIMIZED.bits WindowFlags::MAXIMIZED.bits
); );
const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
} }
@ -114,6 +115,7 @@ impl WindowState {
fullscreen: None, fullscreen: None,
queued_out_of_band_redraw: false, queued_out_of_band_redraw: false,
high_surrogate: None,
window_flags: WindowFlags::empty(), window_flags: WindowFlags::empty(),
} }
} }
@ -122,32 +124,16 @@ impl WindowState {
self.window_flags self.window_flags
} }
pub fn set_window_flags<F>( pub fn set_window_flags<F>(mut this: MutexGuard<'_, Self>, window: HWND, f: F)
mut this: MutexGuard<'_, Self>, where
window: HWND,
set_client_rect: Option<RECT>,
f: F,
) where
F: FnOnce(&mut WindowFlags), F: FnOnce(&mut WindowFlags),
{ {
let old_flags = this.window_flags; let old_flags = this.window_flags;
f(&mut this.window_flags); f(&mut this.window_flags);
let is_fullscreen = this.fullscreen.is_some();
this.window_flags
.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
let new_flags = this.window_flags; let new_flags = this.window_flags;
drop(this); drop(this);
old_flags.apply_diff(window, new_flags, set_client_rect); old_flags.apply_diff(window, new_flags);
}
pub fn refresh_window_state(
this: MutexGuard<'_, Self>,
window: HWND,
set_client_rect: Option<RECT>,
) {
Self::set_window_flags(this, window, set_client_rect, |_| ());
} }
pub fn set_window_flags_in_place<F>(&mut self, f: F) pub fn set_window_flags_in_place<F>(&mut self, f: F)
@ -185,6 +171,7 @@ impl WindowFlags {
fn mask(mut self) -> WindowFlags { fn mask(mut self) -> WindowFlags {
if self.contains(WindowFlags::MARKER_FULLSCREEN) { if self.contains(WindowFlags::MARKER_FULLSCREEN) {
self &= WindowFlags::FULLSCREEN_AND_MASK; self &= WindowFlags::FULLSCREEN_AND_MASK;
self |= WindowFlags::FULLSCREEN_OR_MASK;
} }
if !self.contains(WindowFlags::VISIBLE) { if !self.contains(WindowFlags::VISIBLE) {
self &= WindowFlags::INVISIBLE_AND_MASK; self &= WindowFlags::INVISIBLE_AND_MASK;
@ -236,7 +223,7 @@ impl WindowFlags {
} }
/// Adjust the window client rectangle to the return value, if present. /// Adjust the window client rectangle to the return value, if present.
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) { fn apply_diff(mut self, window: HWND, mut new: WindowFlags) {
self = self.mask(); self = self.mask();
new = new.mask(); new = new.mask();
@ -295,45 +282,20 @@ impl WindowFlags {
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
match set_client_rect let mut flags = winuser::SWP_NOZORDER
.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) | winuser::SWP_NOMOVE
{ | winuser::SWP_NOSIZE
Some(client_rect) => { | winuser::SWP_FRAMECHANGED;
let (x, y, w, h) = (
client_rect.left, // We generally don't want style changes here to affect window
client_rect.top, // focus, but for fullscreen windows they must be activated
client_rect.right - client_rect.left, // (i.e. focused) so that they appear on top of the taskbar
client_rect.bottom - client_rect.top, if !new.contains(WindowFlags::MARKER_FULLSCREEN) {
); flags |= winuser::SWP_NOACTIVATE;
winuser::SetWindowPos(
window,
ptr::null_mut(),
x,
y,
w,
h,
winuser::SWP_NOZORDER
| winuser::SWP_FRAMECHANGED
| winuser::SWP_NOACTIVATE,
);
}
None => {
// Refresh the window frame.
winuser::SetWindowPos(
window,
ptr::null_mut(),
0,
0,
0,
0,
winuser::SWP_NOZORDER
| winuser::SWP_NOMOVE
| winuser::SWP_NOSIZE
| winuser::SWP_FRAMECHANGED
| winuser::SWP_NOACTIVATE,
);
}
} }
// Refresh the window frame
winuser::SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0); winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
} }
} }
@ -345,10 +307,25 @@ impl CursorFlags {
let client_rect = util::get_client_rect(window)?; let client_rect = util::get_client_rect(window)?;
if util::is_focused(window) { if util::is_focused(window) {
if self.contains(CursorFlags::GRABBED) { let cursor_clip = match self.contains(CursorFlags::GRABBED) {
util::set_cursor_clip(Some(client_rect))?; true => Some(client_rect),
} else { false => None,
util::set_cursor_clip(None)?; };
let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom);
let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?);
let desktop_rect = rect_to_tuple(util::get_desktop_rect());
let active_cursor_clip = match desktop_rect == active_cursor_clip {
true => None,
false => Some(active_cursor_clip),
};
// We do this check because calling `set_cursor_clip` incessantly will flood the event
// loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags`
// which at times gets called once every iteration of the eventloop.
if active_cursor_clip != cursor_clip.map(rect_to_tuple) {
util::set_cursor_clip(cursor_clip)?;
} }
} }

View file

@ -5,7 +5,7 @@ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError}, error::{ExternalError, NotSupportedError, OsError},
event_loop::EventLoopWindowTarget, event_loop::EventLoopWindowTarget,
monitor::{AvailableMonitorsIter, MonitorHandle}, monitor::{AvailableMonitorsIter, MonitorHandle, VideoMode},
platform_impl, platform_impl,
}; };
@ -45,6 +45,18 @@ impl fmt::Debug for Window {
} }
} }
impl Drop for Window {
fn drop(&mut self) {
// If the window is in exclusive fullscreen, we must restore the desktop
// video mode (generally this would be done on application exit, but
// closing the window doesn't necessarily always mean application exit,
// such as when there are multiple windows)
if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() {
self.set_fullscreen(None);
}
}
}
/// Identifier of a window. Unique for each window. /// Identifier of a window. Unique for each window.
/// ///
/// Can be obtained with `window.id()`. /// Can be obtained with `window.id()`.
@ -110,7 +122,7 @@ pub struct WindowAttributes {
/// Whether the window should be set as fullscreen upon creation. /// Whether the window should be set as fullscreen upon creation.
/// ///
/// The default is `None`. /// The default is `None`.
pub fullscreen: Option<MonitorHandle>, pub fullscreen: Option<Fullscreen>,
/// The title of the window in the title bar. /// The title of the window in the title bar.
/// ///
@ -168,10 +180,11 @@ impl Default for WindowAttributes {
} }
} }
} }
impl WindowBuilder { impl WindowBuilder {
/// Initializes a new `WindowBuilder` with default values. /// Initializes a new `WindowBuilder` with default values.
#[inline] #[inline]
pub fn new() -> WindowBuilder { pub fn new() -> Self {
WindowBuilder { WindowBuilder {
window: Default::default(), window: Default::default(),
platform_specific: Default::default(), platform_specific: Default::default(),
@ -180,21 +193,21 @@ impl WindowBuilder {
/// Requests the window to be of specific dimensions. /// Requests the window to be of specific dimensions.
#[inline] #[inline]
pub fn with_inner_size(mut self, size: LogicalSize) -> WindowBuilder { pub fn with_inner_size(mut self, size: LogicalSize) -> Self {
self.window.inner_size = Some(size); self.window.inner_size = Some(size);
self self
} }
/// Sets a minimum dimension size for the window /// Sets a minimum dimension size for the window
#[inline] #[inline]
pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> WindowBuilder { pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> Self {
self.window.min_inner_size = Some(min_size); self.window.min_inner_size = Some(min_size);
self self
} }
/// Sets a maximum dimension size for the window /// Sets a maximum dimension size for the window
#[inline] #[inline]
pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> WindowBuilder { pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> Self {
self.window.max_inner_size = Some(max_size); self.window.max_inner_size = Some(max_size);
self self
} }
@ -210,57 +223,61 @@ impl WindowBuilder {
/// ///
/// Due to a bug in XFCE, this has no effect on Xfwm. /// Due to a bug in XFCE, this has no effect on Xfwm.
#[inline] #[inline]
pub fn with_resizable(mut self, resizable: bool) -> WindowBuilder { pub fn with_resizable(mut self, resizable: bool) -> Self {
self.window.resizable = resizable; self.window.resizable = resizable;
self self
} }
/// Requests a specific title for the window. /// Requests a specific title for the window.
#[inline] #[inline]
pub fn with_title<T: Into<String>>(mut self, title: T) -> WindowBuilder { pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
self.window.title = title.into(); self.window.title = title.into();
self self
} }
/// Sets the window fullscreen state. None means a normal window, Some(MonitorHandle) /// Sets the window fullscreen state. None means a normal window, Some(Fullscreen)
/// means a fullscreen window on that specific monitor /// means a fullscreen window on that specific monitor
///
/// ## Platform-specific
///
/// - **Windows:** Screen saver is disabled in fullscreen mode.
#[inline] #[inline]
pub fn with_fullscreen(mut self, monitor: Option<MonitorHandle>) -> WindowBuilder { pub fn with_fullscreen(mut self, monitor: Option<Fullscreen>) -> Self {
self.window.fullscreen = monitor; self.window.fullscreen = monitor;
self self
} }
/// Requests maximized mode. /// Requests maximized mode.
#[inline] #[inline]
pub fn with_maximized(mut self, maximized: bool) -> WindowBuilder { pub fn with_maximized(mut self, maximized: bool) -> Self {
self.window.maximized = maximized; self.window.maximized = maximized;
self self
} }
/// Sets whether the window will be initially hidden or visible. /// Sets whether the window will be initially hidden or visible.
#[inline] #[inline]
pub fn with_visible(mut self, visible: bool) -> WindowBuilder { pub fn with_visible(mut self, visible: bool) -> Self {
self.window.visible = visible; self.window.visible = visible;
self self
} }
/// Sets whether the background of the window should be transparent. /// Sets whether the background of the window should be transparent.
#[inline] #[inline]
pub fn with_transparent(mut self, transparent: bool) -> WindowBuilder { pub fn with_transparent(mut self, transparent: bool) -> Self {
self.window.transparent = transparent; self.window.transparent = transparent;
self self
} }
/// Sets whether the window should have a border, a title bar, etc. /// Sets whether the window should have a border, a title bar, etc.
#[inline] #[inline]
pub fn with_decorations(mut self, decorations: bool) -> WindowBuilder { pub fn with_decorations(mut self, decorations: bool) -> Self {
self.window.decorations = decorations; self.window.decorations = decorations;
self self
} }
/// Sets whether or not the window will always be on top of other windows. /// Sets whether or not the window will always be on top of other windows.
#[inline] #[inline]
pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder { pub fn with_always_on_top(mut self, always_on_top: bool) -> Self {
self.window.always_on_top = always_on_top; self.window.always_on_top = always_on_top;
self self
} }
@ -278,7 +295,7 @@ impl WindowBuilder {
/// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That /// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That
/// said, it's usually in the same ballpark as on Windows. /// said, it's usually in the same ballpark as on Windows.
#[inline] #[inline]
pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> WindowBuilder { pub fn with_window_icon(mut self, window_icon: Option<Icon>) -> Self {
self.window.window_icon = window_icon; self.window.window_icon = window_icon;
self self
} }
@ -291,7 +308,6 @@ impl WindowBuilder {
self, self,
window_target: &EventLoopWindowTarget<T>, window_target: &EventLoopWindowTarget<T>,
) -> Result<Window, OsError> { ) -> Result<Window, OsError> {
// building
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific) platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
.map(|window| Window { window }) .map(|window| Window { window })
} }
@ -398,9 +414,8 @@ impl Window {
/// Modifies the position of the window. /// Modifies the position of the window.
/// ///
/// See `outer_position` for more information about the coordinates. /// See `outer_position` for more information about the coordinates. This automatically un-maximizes the
/// /// window if it's maximized.
/// This is a no-op if the window has already been closed.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
@ -430,7 +445,8 @@ impl Window {
/// Modifies the inner size of the window. /// Modifies the inner size of the window.
/// ///
/// See `inner_size` for more information about the values. /// See `inner_size` for more information about the values. This automatically un-maximizes the
/// window if it's maximized.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
@ -533,10 +549,27 @@ impl Window {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a
/// video mode change. *Caveat!* macOS doesn't provide task switching (or
/// spaces!) while in exclusive fullscreen mode. This mode should be used
/// when a video mode change is desired, but for a better user experience,
/// borderless fullscreen might be preferred.
///
/// `Fullscreen::Borderless` provides a borderless fullscreen window on a
/// separate space. This is the idiomatic way for fullscreen games to work
/// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if
/// separate spaces are not preferred.
///
/// The dock and the menu bar are always disabled in fullscreen mode.
/// - **iOS:** Can only be called on the main thread. /// - **iOS:** Can only be called on the main thread.
/// - **Wayland:** Does not support exclusive fullscreen mode.
/// - **Windows:** Screen saver is disabled in fullscreen mode.
///
/// [simple]:
/// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.window.set_fullscreen(monitor) self.window.set_fullscreen(fullscreen)
} }
/// Gets the window's current fullscreen state. /// Gets the window's current fullscreen state.
@ -545,7 +578,7 @@ impl Window {
/// ///
/// - **iOS:** Can only be called on the main thread. /// - **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<MonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
self.window.fullscreen() self.window.fullscreen()
} }
@ -553,10 +586,7 @@ impl Window {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden /// - **iOS:** Has no effect.
/// via [`setPrefersStatusBarHidden`].
///
/// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc
#[inline] #[inline]
pub fn set_decorations(&self, decorations: bool) { pub fn set_decorations(&self, decorations: bool) {
self.window.set_decorations(decorations) self.window.set_decorations(decorations)
@ -692,6 +722,12 @@ impl Window {
} }
} }
unsafe impl raw_window_handle::HasRawWindowHandle for Window {
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
self.window.raw_window_handle()
}
}
/// Describes the appearance of the mouse cursor. /// Describes the appearance of the mouse cursor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -725,7 +761,9 @@ pub enum CursorIcon {
Alias, Alias,
Copy, Copy,
NoDrop, NoDrop,
/// Indicates something can be grabbed.
Grab, Grab,
/// Indicates something is grabbed.
Grabbing, Grabbing,
AllScroll, AllScroll,
ZoomIn, ZoomIn,
@ -754,3 +792,9 @@ impl Default for CursorIcon {
CursorIcon::Default CursorIcon::Default
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Fullscreen {
Exclusive(VideoMode),
Borderless(MonitorHandle),
}