diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10eabd8f74..493cad3183 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,12 +17,14 @@ jobs: - name: Check Formatting run: cargo +stable fmt --all -- --check - Tests: + tests: + name: Tests strategy: fail-fast: false matrix: - rust_version: [stable, nightly] + rust_version: [1.57.0, stable, nightly] platform: + # Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! - { target: x86_64-pc-windows-msvc, os: windows-latest, } - { target: i686-pc-windows-msvc, os: windows-latest, } - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu } @@ -47,6 +49,7 @@ jobs: OPTIONS: ${{ matrix.platform.options }} FEATURES: ${{ format(',{0}', matrix.platform.features ) }} CMD: ${{ matrix.platform.cmd }} + RUSTDOCFLAGS: -Dwarnings runs-on: ${{ matrix.platform.os }} steps: @@ -62,7 +65,13 @@ jobs: with: rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }} targets: ${{ matrix.platform.target }} + components: clippy + - name: Setup NDK path + shell: bash + # "Temporary" workaround until https://github.com/actions/virtual-environments/issues/5879#issuecomment-1195156618 + # gets looked into. + run: echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> $GITHUB_ENV - name: Install Linux dependencies if: (matrix.platform.os == 'ubuntu-latest') run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev @@ -75,8 +84,7 @@ jobs: - name: Check documentation shell: bash - if: matrix.platform.target != 'wasm32-unknown-unknown' - run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES + run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items - name: Build shell: bash @@ -93,6 +101,10 @@ jobs: !contains(matrix.platform.target, 'wasm32')) run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES + - name: Lint with clippy + shell: bash + if: (matrix.rust_version == '1.57.0') && !contains(matrix.platform.options, '--no-default-features') + run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings - name: Build with serde enabled shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 099994d498..5633f7b23d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,26 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On X11, fix min, max and resize increment hints not persisting for resizable windows (e.g. on DPI change). +- On Windows, respect min/max inner sizes when creating the window. + +# 0.27.1 (2022-07-30) + +- The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested. +- On X11, fix crash on start due to inability to create an IME context without any preedit. + +# 0.27.0 (2022-07-26) + +- On Windows, fix hiding a maximized window. +- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`. +- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception. +- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11. +- On X11, fix events for caps lock key not being sent +- Build docs on `docs.rs` for iOS and Android as well. +- **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`. +- Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute. +- On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q. +- On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning. - On Wayland, fix bug where the cursor wouldn't hide in GNOME. - On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events. - On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`. @@ -20,6 +40,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait` - On X11, fix scale factor calculation when the only monitor is reconnected - On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`. +- On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends. - **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`. - Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable. - **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs. @@ -30,7 +51,6 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods). - **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies. - The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings. -- On Wayland, fix resize and scale factor changes not being propagated properly. - On Wayland, fix polling during consecutive `EventLoop::run_return` invocations. - On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen` - On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found. @@ -51,6 +71,23 @@ And please only add new entries to the top of this list, right below the `# Unre - Added `Window::set_ime_allowed` supported on desktop platforms. - **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled. - On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`. +- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level. +- Implemented `Default` on `EventLoop<()>`. +- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`. +- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior. +- On Wayland, add support for `Window::set_cursor_position`. +- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value. +- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once. +- Added `From` for `WindowId` and `From` for `u64`. +- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate. +- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision. +- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated. +- On Windows, fix focus events being sent to inactive windows. +- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`. +- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib. +- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`. +- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability. +- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down. # 0.26.1 (2022-01-05) @@ -59,7 +96,6 @@ And please only add new entries to the top of this list, right below the `# Unre - On X11, add mappings for numpad comma, numpad enter, numlock and pause. - On macOS, fix Pinyin IME input by reverting a change that intended to improve IME. - On Windows, fix a crash with transparent windows on Windows 11. -- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down. # 0.26.0 (2021-12-01) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f90b74dff..021d8a043e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,7 @@ your description of the issue as detailed as possible: When making a code contribution to winit, before opening your pull request, please make sure that: +- your patch builds with Winit's minimal supported rust version - Rust 1.57.0. - you tested your modifications on all the platforms impacted, or if not possible detail which platforms were not tested, and what should be tested, so that a maintainer or another contributor can test them - you updated any relevant documentation in winit diff --git a/Cargo.toml b/Cargo.toml index 3f5acfb21d..5365442273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.26.1" +version = "0.27.1" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2021" @@ -10,11 +10,28 @@ readme = "README.md" repository = "https://github.com/rust-windowing/winit" documentation = "https://docs.rs/winit" categories = ["gui"] +rust-version = "1.57.0" [package.metadata.docs.rs] features = ["serde"] default-target = "x86_64-unknown-linux-gnu" -targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"] +# These are all tested in CI +targets = [ + # Windows + "i686-pc-windows-msvc", + "x86_64-pc-windows-msvc", + # macOS + "x86_64-apple-darwin", + # Unix (X11 & Wayland) + "i686-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + # iOS + "x86_64-apple-ios", + # Android + "aarch64-linux-android", + # WebAssembly + "wasm32-unknown-unknown", +] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] @@ -26,10 +43,10 @@ wayland-csd-adwaita-notitle = ["sctk-adwaita"] [dependencies] instant = { version = "0.1", features = ["wasm-bindgen"] } -lazy_static = "1" +once_cell = "1.12" log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } -raw-window-handle = "0.4.2" +raw-window-handle = "0.5.0" bitflags = "1" mint = { version = "0.5.6", optional = true } @@ -38,9 +55,9 @@ image = { version = "0.24.0", default-features = false, features = ["png"] } simple_logger = "2.1.0" [target.'cfg(target_os = "android")'.dependencies] -ndk = "0.6" -ndk-sys = "0.3" -ndk-glue = "0.6" +# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995 +ndk = "0.7.0" +ndk-glue = "0.7.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] objc = "0.2.7" @@ -51,11 +68,6 @@ core-foundation = "0.9" core-graphics = "0.22" dispatch = "0.2.0" -[target.'cfg(target_os = "macos")'.dependencies.core-video-sys] -version = "0.1.4" -default_features = false -features = ["display_link"] - [target.'cfg(target_os = "windows")'.dependencies] parking_lot = "0.12" @@ -91,8 +103,8 @@ features = [ [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true } wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true } -sctk = { package = "smithay-client-toolkit", version = "0.15.4", default_features = false, features = ["calloop"], optional = true } -sctk-adwaita = { version = "0.3.5", optional = true } +sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true } +sctk-adwaita = { version = "0.4.1", optional = true } mio = { version = "0.8", features = ["os-ext"], optional = true } x11-dl = { version = "2.18.5", optional = true } percent-encoding = { version = "2.0", optional = true } diff --git a/FEATURES.md b/FEATURES.md index 02625f064e..3610f4b793 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -100,7 +100,8 @@ If your PR makes notable changes to Winit's features, please update this section ### Input Handling - **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events. - **Mouse set location**: Forcibly changing the location of the pointer. -- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window. +- **Cursor locking**: Locking the cursor inside the window so it cannot move. +- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them. - **Cursor icon**: Changing the cursor icon, or hiding the cursor. - **Cursor hittest**: Handle or ignore mouse events for a window. - **Touch events**: Single-touch events. @@ -197,8 +198,9 @@ Legend: |Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ | -|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**| -|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|✔️ | +|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**| +|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ | +|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | |Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | diff --git a/README.md b/README.md index 88e44f80f8..c42910fd5d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.26.1" +winit = "0.27.1" ``` ## [Documentation](https://docs.rs/winit) @@ -69,6 +69,14 @@ Winit provides the following features, which can be enabled in your `Cargo.toml` ### Platform-specific usage +#### Wayland + +Note that windows don't appear on Wayland until you draw/present to them. + +`winit` doesn't do drawing, try the examples in [`glutin`] instead. + +[`glutin`]: https://github.com/rust-windowing/glutin + #### WebAssembly To run the web example: `cargo run-wasm --example web` @@ -93,16 +101,16 @@ book]. This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation. -The `ndk_glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk_glue`'s internal NativeActivity static is not the same due to version mismatch. +The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch. -`ndk_glue` <-> `winit` version comparison compatibility: +`winit` compatibility table with `ndk-glue`: -| winit | ndk_glue | +| winit | ndk-glue | | :---: | :------------------: | -| 0.24 | `ndk_glue = "0.2.0"` | -| 0.25 | `ndk_glue = "0.3.0"` | -| 0.26 | `ndk_glue = "0.5.0"` | -| 0.27 | `ndk_glue = "0.6.0"` | +| 0.24 | `ndk-glue = "0.2.0"` | +| 0.25 | `ndk-glue = "0.3.0"` | +| 0.26 | `ndk-glue = "0.5.0"` | +| 0.27 | `ndk-glue = "0.7.0"` | Running on an Android device needs a dynamic system library, add this to Cargo.toml: @@ -121,3 +129,16 @@ fn main() { ``` And run the application with `cargo apk run --example request_redraw_threaded` + +#### MacOS + +A lot of functionality expects the application to be ready before you start +doing anything; this includes creating windows, fetching monitors, drawing, +and so on, see issues [#2238], [#2051] and [#2087]. + +If you encounter problems, you should try doing your initialization inside +`Event::NewEvents(StartCause::Init)`. + +[#2238]: https://github.com/rust-windowing/winit/issues/2238 +[#2051]: https://github.com/rust-windowing/winit/issues/2051 +[#2087]: https://github.com/rust-windowing/winit/issues/2087 diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 0946dbe794..94926d7764 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use std::{thread, time}; use simple_logger::SimpleLogger; diff --git a/examples/cursor.rs b/examples/cursor.rs index 86bed427c8..fdef7ef971 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyboardInput, WindowEvent}, diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 979fed65e1..cff77885ea 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -1,8 +1,10 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, event_loop::EventLoop, - window::WindowBuilder, + window::{CursorGrabMode, WindowBuilder}, }; fn main() { @@ -32,11 +34,23 @@ fn main() { .. } => { use winit::event::VirtualKeyCode::*; - match key { - Escape => control_flow.set_exit(), - G => window.set_cursor_grab(!modifiers.shift()).unwrap(), - H => window.set_cursor_visible(modifiers.shift()), - _ => (), + let result = match key { + Escape => { + control_flow.set_exit(); + Ok(()) + } + G => window.set_cursor_grab(CursorGrabMode::Confined), + L => window.set_cursor_grab(CursorGrabMode::Locked), + A => window.set_cursor_grab(CursorGrabMode::None), + H => { + window.set_cursor_visible(modifiers.shift()); + Ok(()) + } + _ => Ok(()), + }; + + if let Err(err) = result { + println!("error: {}", err); } } WindowEvent::ModifiersChanged(m) => modifiers = m, diff --git a/examples/custom_events.rs b/examples/custom_events.rs index 5ba5d4e341..c1b7133de6 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + #[cfg(not(target_arch = "wasm32"))] fn main() { use simple_logger::SimpleLogger; diff --git a/examples/drag_window.rs b/examples/drag_window.rs index 753958c8f1..813e9b00c9 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{ diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index b5a9ebb2de..6bfda4c2c9 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,37 +1,44 @@ -use std::io::{stdin, stdout, Write}; +#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event_loop::EventLoop; -use winit::monitor::{MonitorHandle, VideoMode}; use winit::window::{Fullscreen, WindowBuilder}; fn main() { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); - print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: "); - stdout().flush().unwrap(); - - let mut num = String::new(); - stdin().read_line(&mut num).unwrap(); - let num = num.trim().parse().expect("Please enter a number"); - - let fullscreen = Some(match num { - 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))), - 2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))), - _ => panic!("Please enter a valid number"), - }); - let mut decorations = true; + let mut minimized = false; let window = WindowBuilder::new() .with_title("Hello world!") - .with_fullscreen(fullscreen.clone()) .build(&event_loop) .unwrap(); - event_loop.run(move |event, _, control_flow| { + let mut monitor_index = 0; + let mut monitor = event_loop + .available_monitors() + .next() + .expect("no monitor found!"); + println!("Monitor: {:?}", monitor.name()); + + let mut mode_index = 0; + let mut mode = monitor.video_modes().next().expect("no mode found"); + println!("Mode: {}", mode); + + println!("Keys:"); + println!("- Esc\tExit"); + println!("- F\tToggle exclusive fullscreen mode"); + println!("- B\tToggle borderless mode"); + println!("- S\tNext screen"); + println!("- M\tNext mode for this screen"); + println!("- D\tToggle window decorations"); + println!("- X\tMaximize window"); + println!("- Z\tMinimize window"); + + event_loop.run(move |event, elwt, control_flow| { control_flow.set_wait(); match event { @@ -41,29 +48,60 @@ fn main() { input: KeyboardInput { virtual_keycode: Some(virtual_code), - state, + state: ElementState::Pressed, .. }, .. - } => match (virtual_code, state) { - (VirtualKeyCode::Escape, _) => control_flow.set_exit(), - (VirtualKeyCode::F, ElementState::Pressed) => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); + } => match virtual_code { + VirtualKeyCode::Escape => control_flow.set_exit(), + VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => { + window.set_fullscreen(None); + } + VirtualKeyCode::F => { + let fullscreen = Some(Fullscreen::Exclusive(mode.clone())); + println!("Setting mode: {:?}", fullscreen); + window.set_fullscreen(fullscreen); + } + VirtualKeyCode::B => { + let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone()))); + println!("Setting mode: {:?}", fullscreen); + window.set_fullscreen(fullscreen); + } + VirtualKeyCode::S => { + monitor_index += 1; + if let Some(mon) = elwt.available_monitors().nth(monitor_index) { + monitor = mon; } else { - window.set_fullscreen(fullscreen.clone()); + monitor_index = 0; + monitor = elwt.available_monitors().next().expect("no monitor found!"); } + println!("Monitor: {:?}", monitor.name()); + + mode_index = 0; + mode = monitor.video_modes().next().expect("no mode found"); + println!("Mode: {}", mode); } - (VirtualKeyCode::S, ElementState::Pressed) => { - println!("window.fullscreen {:?}", window.fullscreen()); + VirtualKeyCode::M => { + mode_index += 1; + if let Some(m) = monitor.video_modes().nth(mode_index) { + mode = m; + } else { + mode_index = 0; + mode = monitor.video_modes().next().expect("no mode found"); + } + println!("Mode: {}", mode); } - (VirtualKeyCode::M, ElementState::Pressed) => { + VirtualKeyCode::D => { + decorations = !decorations; + window.set_decorations(decorations); + } + VirtualKeyCode::X => { let is_maximized = window.is_maximized(); window.set_maximized(!is_maximized); } - (VirtualKeyCode::D, ElementState::Pressed) => { - decorations = !decorations; - window.set_decorations(decorations); + VirtualKeyCode::Z => { + minimized = !minimized; + window.set_minimized(minimized); } _ => (), }, @@ -73,46 +111,3 @@ fn main() { } }); } - -// Enumerate monitors and prompt user to choose one -fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { - for (num, monitor) in event_loop.available_monitors().enumerate() { - println!("Monitor #{}: {:?}", num, monitor.name()); - } - - print!("Please write the number of the monitor to use: "); - stdout().flush().unwrap(); - - let mut num = String::new(); - stdin().read_line(&mut num).unwrap(); - let num = num.trim().parse().expect("Please enter a number"); - let monitor = event_loop - .available_monitors() - .nth(num) - .expect("Please enter a valid ID"); - - println!("Using {:?}", monitor.name()); - - 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().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 -} diff --git a/examples/handling_close.rs b/examples/handling_close.rs index 2fd2d084b7..1fe4ad3708 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{Event, KeyboardInput, WindowEvent}, diff --git a/examples/ime.rs b/examples/ime.rs index 5f38447a85..bdf67a8624 100644 --- a/examples/ime.rs +++ b/examples/ime.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use log::LevelFilter; use simple_logger::SimpleLogger; use winit::{ diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs deleted file mode 100644 index 56877999e0..0000000000 --- a/examples/min_max_size.rs +++ /dev/null @@ -1,30 +0,0 @@ -use simple_logger::SimpleLogger; -use winit::{ - dpi::LogicalSize, - event::{Event, WindowEvent}, - event_loop::EventLoop, - window::WindowBuilder, -}; - -fn main() { - SimpleLogger::new().init().unwrap(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new().build(&event_loop).unwrap(); - - window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0))); - window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0))); - - event_loop.run(move |event, _, control_flow| { - control_flow.set_wait(); - println!("{:?}", event); - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => control_flow.set_exit(), - _ => (), - } - }); -} diff --git a/examples/minimize.rs b/examples/minimize.rs deleted file mode 100644 index 8af561eeaa..0000000000 --- a/examples/minimize.rs +++ /dev/null @@ -1,41 +0,0 @@ -extern crate winit; - -use simple_logger::SimpleLogger; -use winit::event::{Event, VirtualKeyCode, WindowEvent}; -use winit::event_loop::EventLoop; -use winit::window::WindowBuilder; - -fn main() { - SimpleLogger::new().init().unwrap(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - control_flow.set_wait(); - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => control_flow.set_exit(), - - // Keyboard input event to handle minimize via a hotkey - Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, - window_id, - } => { - if window_id == window.id() { - // Pressing the 'M' key will minimize the window - if input.virtual_keycode == Some(VirtualKeyCode::M) { - window.set_minimized(true); - } - } - } - _ => (), - } - }); -} diff --git a/examples/monitor_list.rs b/examples/monitor_list.rs index 9c8b77e61c..fc52fa5e3f 100644 --- a/examples/monitor_list.rs +++ b/examples/monitor_list.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{event_loop::EventLoop, window::WindowBuilder}; diff --git a/examples/mouse_wheel.rs b/examples/mouse_wheel.rs index d6e665b1a2..c1d8f0cb99 100644 --- a/examples/mouse_wheel.rs +++ b/examples/mouse_wheel.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 9ed28cdbd6..9dc125079b 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + #[cfg(not(target_arch = "wasm32"))] fn main() { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; @@ -7,7 +9,7 @@ fn main() { dpi::{PhysicalPosition, PhysicalSize, Position, Size}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::EventLoop, - window::{CursorIcon, Fullscreen, WindowBuilder}, + window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder}, }; const WINDOW_COUNT: usize = 3; @@ -86,7 +88,21 @@ fn main() { } (false, _) => None, }), - G => window.set_cursor_grab(state).unwrap(), + L if state => { + if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) { + println!("error: {}", err); + } + } + G if state => { + if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) { + println!("error: {}", err); + } + } + G | L if !state => { + if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) { + println!("error: {}", err); + } + } H => window.set_cursor_visible(!state), I => { println!("Info:"); diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index c6bff90afc..de87962d04 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -1,8 +1,10 @@ +#![allow(clippy::single_match)] + use std::collections::HashMap; use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::EventLoop, window::Window, }; @@ -14,9 +16,12 @@ fn main() { let mut windows = HashMap::new(); for _ in 0..3 { let window = Window::new(&event_loop).unwrap(); + println!("Opened a new window: {:?}", window.id()); windows.insert(window.id(), window); } + println!("Press N to open a new window."); + event_loop.run(move |event, event_loop, control_flow| { control_flow.set_wait(); @@ -37,11 +42,14 @@ fn main() { input: KeyboardInput { state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::N), .. }, + is_synthetic: false, .. } => { let window = Window::new(event_loop).unwrap(); + println!("Opened a new window: {:?}", window.id()); windows.insert(window.id(), window); } _ => (), diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index 28275a29f6..93bb43983e 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, WindowEvent}, diff --git a/examples/request_redraw_threaded.rs b/examples/request_redraw_threaded.rs index f6fba32afb..2500a8c02c 100644 --- a/examples/request_redraw_threaded.rs +++ b/examples/request_redraw_threaded.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + #[cfg(not(target_arch = "wasm32"))] fn main() { use std::{thread, time}; @@ -28,10 +30,10 @@ fn main() { control_flow.set_wait(); match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => control_flow.set_exit(), - _ => (), - }, + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => control_flow.set_exit(), Event::RedrawRequested(_) => { println!("\nredrawing!\n"); } diff --git a/examples/resizable.rs b/examples/resizable.rs index 07678756b9..9e438a51d9 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, @@ -14,7 +16,9 @@ fn main() { let window = WindowBuilder::new() .with_title("Hit space to toggle resizability.") - .with_inner_size(LogicalSize::new(400.0, 200.0)) + .with_inner_size(LogicalSize::new(600.0, 300.0)) + .with_min_inner_size(LogicalSize::new(400.0, 200.0)) + .with_max_inner_size(LogicalSize::new(800.0, 400.0)) .with_resizable(resizable) .build(&event_loop) .unwrap(); diff --git a/examples/timer.rs b/examples/timer.rs index 6ea4fc09ea..c312460b22 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use instant::Instant; use std::time::Duration; diff --git a/examples/transparent.rs b/examples/transparent.rs index bed8de72b0..9080cbef12 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, diff --git a/examples/video_modes.rs b/examples/video_modes.rs index 341f43855b..afc4ad6be6 100644 --- a/examples/video_modes.rs +++ b/examples/video_modes.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::event_loop::EventLoop; diff --git a/examples/web.rs b/examples/web.rs index 600a3d8d74..71e5dfd60a 100644 --- a/examples/web.rs +++ b/examples/web.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, @@ -43,6 +45,7 @@ mod wasm { pub fn run() { console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); + #[allow(clippy::main_recursion)] super::main(); } diff --git a/examples/window.rs b/examples/window.rs index 0d0fd74a46..23a633d2ed 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 8c4f42c366..47a130178c 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -1,10 +1,12 @@ +#![allow(clippy::single_match)] + // This example is used by developers to test various window functions. use simple_logger::SimpleLogger; use winit::{ dpi::{LogicalSize, PhysicalSize}, event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::EventLoop, + event_loop::{DeviceEventFilter, EventLoop}, window::{Fullscreen, WindowBuilder}, }; @@ -30,6 +32,8 @@ fn main() { let mut minimized = false; let mut visible = true; + event_loop.set_device_event_filter(DeviceEventFilter::Never); + event_loop.run(move |event, _, control_flow| { control_flow.set_wait(); @@ -58,61 +62,63 @@ fn main() { _ => (), }, Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state: ElementState::Pressed, + .. + }, + .. + }, .. - } => match input { - KeyboardInput { - virtual_keycode: Some(key), - state: ElementState::Pressed, - .. - } => match key { - VirtualKeyCode::E => { - fn area(size: PhysicalSize) -> u32 { - size.width * size.height - } - - let monitor = window.current_monitor().unwrap(); - if let Some(mode) = monitor - .video_modes() - .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) - { - window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); - } else { - eprintln!("no video modes available"); - } - } - VirtualKeyCode::F => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - let monitor = window.current_monitor(); - window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); - } - } - VirtualKeyCode::P => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - window.set_fullscreen(Some(Fullscreen::Borderless(None))); - } - } - VirtualKeyCode::M => { - minimized = !minimized; - window.set_minimized(minimized); + } => match key { + VirtualKeyCode::E => { + fn area(size: PhysicalSize) -> u32 { + size.width * size.height } - VirtualKeyCode::Q => { - control_flow.set_exit(); + + let monitor = window.current_monitor().unwrap(); + if let Some(mode) = monitor + .video_modes() + .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) + { + window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); + } else { + eprintln!("no video modes available"); } - VirtualKeyCode::V => { - visible = !visible; - window.set_visible(visible); + } + VirtualKeyCode::F => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + let monitor = window.current_monitor(); + window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } - VirtualKeyCode::X => { - let is_maximized = window.is_maximized(); - window.set_maximized(!is_maximized); + } + VirtualKeyCode::P => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); } - _ => (), - }, + } + VirtualKeyCode::M => { + minimized = !minimized; + window.set_minimized(minimized); + } + VirtualKeyCode::Q => { + control_flow.set_exit(); + } + VirtualKeyCode::V => { + visible = !visible; + window.set_visible(visible); + } + VirtualKeyCode::X => { + let is_maximized = window.is_maximized(); + window.set_maximized(!is_maximized); + } _ => (), }, Event::WindowEvent { diff --git a/examples/window_icon.rs b/examples/window_icon.rs index c120ed904b..ed98b3d577 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -1,4 +1,5 @@ -extern crate image; +#![allow(clippy::single_match)] + use std::path::Path; use simple_logger::SimpleLogger; diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 81ddc5a225..e6b3c66211 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -1,3 +1,5 @@ +#![allow(clippy::single_match)] + // Limit this example to only compatible platforms. #[cfg(any( target_os = "windows", @@ -6,7 +8,8 @@ target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "android", ))] fn main() { use std::{thread::sleep, time::Duration}; @@ -57,7 +60,7 @@ fn main() { } } -#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))] +#[cfg(any(target_os = "ios", target_arch = "wasm32"))] fn main() { println!("This platform doesn't support run_return."); } diff --git a/src/dpi.rs b/src/dpi.rs index be6d8177f8..7d372d8899 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -35,8 +35,9 @@ //! //! ### Position and Size types //! -//! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the -//! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor. +//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the +//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels +//! divided by the scale factor. //! All of Winit's functions return physical types, but can take either logical or physical //! coordinates as input, allowing you to use the most convenient coordinate system for your //! particular application. @@ -46,19 +47,18 @@ //! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch //! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so //! will truncate the fractional part of the float, rather than properly round to the nearest -//! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the +//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the //! rounding properly. Note that precision loss will still occur when rounding from a float to an //! int, although rounding lessens the problem. //! //! ### Events //! -//! Winit will dispatch a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) -//! event whenever a window's scale factor has changed. This can happen if the user drags their -//! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their -//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how -//! the platform changes the window's size to reflect the new scale factor. If a window hasn't -//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event, -//! then its scale factor can be found by calling [window.scale_factor()]. +//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. +//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI +//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your +//! application's UI elements and adjust how the platform changes the window's size to reflect the new +//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor +//! can be found by calling [`window.scale_factor()`]. //! //! ## How is the scale factor calculated? //! @@ -77,7 +77,7 @@ //! currently uses a three-pronged approach: //! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present. //! + If not present, use the value set in `Xft.dpi` in Xresources. -//! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR. +//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR. //! //! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the //! XRandR scaling method. Generally speaking, you should try to configure the standard system @@ -93,9 +93,11 @@ //! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by //! both the screen scaling and the browser zoom level and can go below `1.0`. //! +//! //! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) -//! [window.scale_factor()]: crate::window::Window::scale_factor +//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged +//! [`window.scale_factor()`]: crate::window::Window::scale_factor //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ @@ -509,6 +511,29 @@ impl Size { Size::Logical(size) => size.to_physical(scale_factor), } } + + pub fn clamp>(input: S, min: S, max: S, scale_factor: f64) -> Size { + let (input, min, max) = ( + input.into().to_physical::(scale_factor), + min.into().to_physical::(scale_factor), + max.into().to_physical::(scale_factor), + ); + + let clamp = |input: f64, min: f64, max: f64| { + if input < min { + min + } else if input > max { + max + } else { + input + } + }; + + let width = clamp(input.width, min.width, max.width); + let height = clamp(input.height, min.height, max.height); + + PhysicalSize::new(width, height).into() + } } impl From> for Size { diff --git a/src/event.rs b/src/event.rs index 65e556a2f7..25cae732ba 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,10 +1,10 @@ -//! The `Event` enum and assorted supporting types. +//! The [`Event`] enum and assorted supporting types. //! -//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get +//! These are sent to the closure given to [`EventLoop::run(...)`], where they get //! processed and used to modify the program state. For more details, see the root-level documentation. //! //! Some of these events represent different "parts" of a traditional event-handling loop. You could -//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this: +//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this: //! //! ```rust,ignore //! let mut control_flow = ControlFlow::Poll; @@ -29,10 +29,11 @@ //! event_handler(LoopDestroyed, ..., &mut control_flow); //! ``` //! -//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully +//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully //! describes what happens in what order. //! -//! [event_loop_run]: crate::event_loop::EventLoop::run +//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run +//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil use instant::Instant; use std::path::PathBuf; @@ -73,9 +74,107 @@ pub enum Event<'a, T: 'static> { UserEvent(T), /// Emitted when the application has been suspended. + /// + /// # Portability + /// + /// Not all platforms support the notion of suspending applications, and there may be no + /// technical way to guarantee being able to emit a `Suspended` event if the OS has + /// no formal application lifecycle (currently only Android and iOS do). For this reason, + /// Winit does not currently try to emit pseudo `Suspended` events before the application + /// quits on platforms without an application lifecycle. + /// + /// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally + /// driven by multiple platform-specific events, and that there may be subtle differences across + /// platforms with how these internal events are delivered, it's recommended that applications + /// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events. + /// + /// Also see [`Resumed`] notes. + /// + /// ## Android + /// + /// On Android, the `Suspended` event is only sent when the application's associated + /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`] + /// lifecycle event but there may technically be a discrepancy. + /// + /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() + /// + /// Applications that need to run on Android should assume their [`SurfaceView`] has been + /// destroyed, which indirectly invalidates any existing render surfaces that may have been + /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). + /// + /// After being `Suspended` on Android applications must drop all render surfaces before + /// the event callback completes, which may be re-created when the application is next [`Resumed`]. + /// + /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView + /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle + /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html + /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html + /// + /// ## iOS + /// + /// On iOS, the `Suspended` event is currently emitted in response to an + /// [`applicationWillResignActive`] callback which means that the application is + /// about to transition from the active to inactive state (according to the + /// [iOS application lifecycle]). + /// + /// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive + /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle + /// + /// [`Resumed`]: Self::Resumed Suspended, /// Emitted when the application has been resumed. + /// + /// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a + /// formal suspend/resume lifecycle. For systems without a standard suspend/resume lifecycle + /// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init] + /// event. + /// + /// # Portability + /// + /// It's recommended that applications should only initialize their graphics context and create + /// a window after they have received their first `Resumed` event. Some systems + /// (specifically Android) won't allow applications to create a render surface until they are + /// resumed. + /// + /// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally + /// driven by multiple platform-specific events, and that there may be subtle differences across + /// platforms with how these internal events are delivered, it's recommended that applications + /// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events. + /// + /// Also see [`Suspended`] notes. + /// + /// ## Android + /// + /// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is + /// expected to closely correlate with the [`onResume`] lifecycle event but there may technically + /// be a discrepancy. + /// + /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() + /// + /// Applications that need to run on Android must wait until they have been `Resumed` + /// before they will be able to create a render surface (such as an `EGLSurface`, + /// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a + /// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their + /// render surfaces are invalid and should be dropped. + /// + /// Also see [`Suspended`] notes. + /// + /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView + /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle + /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html + /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html + /// + /// ## iOS + /// + /// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`] + /// callback which means the application is "active" (according to the + /// [iOS application lifecycle]). + /// + /// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive + /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle + /// + /// [`Suspended`]: Self::Suspended Resumed, /// Emitted when all of the event loop's input events have been processed and redraw processing @@ -90,7 +189,7 @@ pub enum Event<'a, T: 'static> { /// can render here unconditionally for simplicity. MainEventsCleared, - /// Emitted after `MainEventsCleared` when a window should be redrawn. + /// Emitted after [`MainEventsCleared`] when a window should be redrawn. /// /// This gets triggered in two scenarios: /// - The OS has performed an operation that's invalidated the window's contents (such as @@ -102,14 +201,18 @@ pub enum Event<'a, T: 'static> { /// /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless /// something changes, like most non-game GUIs. + /// + /// [`MainEventsCleared`]: Self::MainEventsCleared RedrawRequested(WindowId), - /// Emitted after all `RedrawRequested` events have been processed and control flow is about to + /// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted /// immediately after `MainEventsCleared`. /// /// This event is useful for doing any cleanup or bookkeeping work after all the rendering /// tasks have been completed. + /// + /// [`RedrawRequested`]: Self::RedrawRequested RedrawEventsCleared, /// Emitted when the event loop is being shut down. @@ -184,9 +287,11 @@ impl<'a, T> Event<'a, T> { /// Describes the reason the event loop is resuming. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StartCause { - /// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the + /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the /// moment the timeout was requested and the requested resume time. The actual resume time is /// guaranteed to be equal to or after the requested resume time. + /// + /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil ResumeTimeReached { start: Instant, requested_resume: Instant, @@ -200,7 +305,9 @@ pub enum StartCause { }, /// Sent if the event loop is being resumed after the loop's control flow was set to - /// `ControlFlow::Poll`. + /// [`ControlFlow::Poll`]. + /// + /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll Poll, /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. @@ -268,17 +375,19 @@ pub enum WindowEvent<'a> { /// The keyboard modifiers have changed. /// - /// Platform-specific behavior: - /// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an + /// ## Platform-specific + /// + /// - **Web:** This API is currently unimplemented on the web. This isn't by design - it's an /// issue, and it should get fixed - but it's the current state of the API. ModifiersChanged(ModifiersState), - /// An event from input method. + /// An event from an input method. /// - /// **Note :** You have to explicitly enable this event using [`Window::set_ime_allowed`]. + /// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`]. /// - /// Platform-specific behavior: - /// - **iOS / Android / Web :** Unsupported. + /// ## Platform-specific + /// + /// - **iOS / Android / Web:** Unsupported. Ime(Ime), /// The cursor has moved on the window. @@ -361,8 +470,19 @@ pub enum WindowEvent<'a> { /// Applications might wish to react to this to change the theme of the content of the window /// when the system changes the window theme. /// + /// ## Platform-specific + /// /// At the moment this is only supported on Windows. ThemeChanged(Theme), + + /// The window has been occluded (completely hidden from view). + /// + /// This is different to window visibility as it depends on whether the window is closed, + /// minimised, set invisible, or fully occluded by another window. + /// + /// Platform-specific behavior: + /// - **iOS / Android / Web / Wayland / Windows:** Unsupported. + Occluded(bool), } impl Clone for WindowEvent<'static> { @@ -452,6 +572,7 @@ impl Clone for WindowEvent<'static> { ScaleFactorChanged { .. } => { unreachable!("Static event can't be about scale factor changing") } + Occluded(occluded) => Occluded(*occluded), }; } } @@ -537,6 +658,7 @@ impl<'a> WindowEvent<'a> { Touch(touch) => Some(Touch(touch)), ThemeChanged(theme) => Some(ThemeChanged(theme)), ScaleFactorChanged { .. } => None, + Occluded(occluded) => Some(Occluded(occluded)), } } } @@ -550,7 +672,7 @@ impl<'a> WindowEvent<'a> { pub struct DeviceId(pub(crate) platform_impl::DeviceId); impl DeviceId { - /// Returns a dummy `DeviceId`, useful for unit testing. + /// Returns a dummy id, useful for unit testing. /// /// # Safety /// @@ -579,7 +701,7 @@ pub enum DeviceEvent { /// Change in physical position of a pointing device. /// - /// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`. + /// This represents raw, unfiltered physical motion. Not to be confused with [`WindowEvent::CursorMoved`]. MouseMotion { /// (x, y) change in position in unspecified units. /// @@ -592,7 +714,7 @@ pub enum DeviceEvent { delta: MouseScrollDelta, }, - /// Motion on some analog axis. This event will be reported for all arbitrary input devices + /// Motion on some analog axis. This event will be reported for all arbitrary input devices /// that winit supports on this platform, including mouse devices. If the device is a mouse /// device then this will be reported alongside the MouseMotion event. Motion { @@ -673,7 +795,6 @@ pub struct KeyboardInput { /// // Press space key /// Ime::Commit("啊不") /// ``` -/// #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Ime { @@ -718,18 +839,18 @@ pub enum TouchPhase { /// Represents a touch event /// -/// Every time the user touches the screen, a new `Start` event with an unique -/// identifier for the finger is generated. When the finger is lifted, an `End` +/// Every time the user touches the screen, a new [`TouchPhase::Started`] event with an unique +/// identifier for the finger is generated. When the finger is lifted, an [`TouchPhase::Ended`] /// event is generated with the same finger id. /// -/// After a `Start` event has been emitted, there may be zero or more `Move` +/// After a `Started` event has been emitted, there may be zero or more `Move` /// events when the finger is moved or the touch pressure changes. /// -/// The finger id may be reused by the system after an `End` event. The user -/// should assume that a new `Start` event received with the same id has nothing +/// The finger id may be reused by the system after an `Ended` event. The user +/// should assume that a new `Started` event received with the same id has nothing /// to do with the old finger and is a new finger. /// -/// A `Cancelled` event is emitted when the system has canceled tracking this +/// A [`TouchPhase::Cancelled`] event is emitted when the system has canceled tracking this /// touch, such as when the window loses focus, or on iOS if the user moves the /// device against their face. #[derive(Debug, Clone, Copy, PartialEq)] @@ -783,8 +904,9 @@ pub enum Force { 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 + /// [`Force::Calibrated`] so that the amount of force the user has to apply is /// consistent across devices. pub fn normalized(&self) -> f64 { match self { @@ -845,7 +967,7 @@ pub enum MouseScrollDelta { /// Amount in pixels to scroll in the horizontal and /// vertical direction. /// - /// Scroll events are expressed as a PixelDelta if + /// Scroll events are expressed as a `PixelDelta` if /// supported by the device (eg. a touchpad) and /// platform. /// diff --git a/src/event_loop.rs b/src/event_loop.rs index d6a15cfa9a..b96c80c9bc 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -1,44 +1,46 @@ -//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`. +//! The [`EventLoop`] struct and assorted supporting types, including +//! [`ControlFlow`]. //! -//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy] -//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method. +//! If you want to send custom events to the event loop, use +//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its +//! [`send_event`](`EventLoopProxy::send_event`) method. //! //! See the root-level documentation for information on how to create and use an event loop to //! handle events. -//! -//! [create_proxy]: crate::event_loop::EventLoop::create_proxy -//! [event_loop_proxy]: crate::event_loop::EventLoopProxy -//! [send_event]: crate::event_loop::EventLoopProxy::send_event -use instant::Instant; use std::marker::PhantomData; use std::ops::Deref; use std::{error, fmt}; +use instant::Instant; +use once_cell::sync::OnceCell; +use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle}; + use crate::{event::Event, monitor::MonitorHandle, platform_impl}; /// Provides a way to retrieve events from the system and from the windows that were registered to /// the events loop. /// -/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()` +/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`] /// initializes everything that will be required to create windows. For example on Linux creating /// an event loop opens a connection to the X or Wayland server. /// -/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs. +/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs. /// -/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic -/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the -/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the -/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread. +/// Note that this cannot be shared across threads (due to platform-dependant logic +/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the +/// [`Window`] created from this _can_ be sent to an other thread, and the +/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread. /// +/// [`Window`]: crate::window::Window pub struct EventLoop { pub(crate) event_loop: platform_impl::EventLoop, pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync } -/// 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. `EventLoop` will coerce into this type (`impl Deref for +/// your callback. [`EventLoop`] will coerce into this type (`impl Deref for /// EventLoop`), so functions that take this as a parameter can also take /// `&EventLoop`. pub struct EventLoopWindowTarget { @@ -77,11 +79,14 @@ impl EventLoopBuilder { /// Builds a new event loop. /// - /// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.*** - /// Attempting to create the event loop on a different thread will panic. This restriction isn't + /// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread, + /// and only once per application.*** + /// + /// Attempting to create the event loop on a different thread, or multiple event loops in + /// the same application, will panic. This restriction isn't /// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when /// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed - /// in the relevant `platform` module if the target platform supports creating an event loop on + /// in the relevant [`platform`] module if the target platform supports creating an event loop on /// any thread. /// /// Calling this function will result in display backend initialisation. @@ -92,8 +97,16 @@ impl EventLoopBuilder { /// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. /// If it is not set, winit will try to connect to a Wayland connection, and if that fails, /// will fall back on X11. If this variable is set with any other value, winit will panic. + /// + /// [`platform`]: crate::platform #[inline] pub fn build(&mut self) -> EventLoop { + static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new(); + if EVENT_LOOP_CREATED.set(()).is_err() { + panic!("Creating EventLoop multiple times is not supported."); + } + // Certain platforms accept a mutable reference in their API. + #[allow(clippy::unnecessary_mut_passed)] EventLoop { event_loop: platform_impl::EventLoop::new(&mut self.platform_specific), _marker: PhantomData, @@ -113,24 +126,28 @@ impl fmt::Debug for EventLoopWindowTarget { } } -/// Set by the user callback given to the `EventLoop::run` method. +/// Set by the user callback given to the [`EventLoop::run`] method. /// -/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared] -/// is emitted. Defaults to `Poll`. +/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted. +/// +/// Defaults to [`Poll`]. /// /// ## Persistency +/// /// Almost every change is persistent between multiple calls to the event loop closure within a -/// given run loop. The only exception to this is `ExitWithCode` which, once set, cannot be unset. +/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset. /// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will -/// reset the control flow to `Poll`. +/// reset the control flow to [`Poll`]. /// -/// [events_cleared]: crate::event::Event::RedrawEventsCleared +/// [`ExitWithCode`]: Self::ExitWithCode +/// [`Poll`]: Self::Poll #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of /// whether or not new events are available to process. /// /// ## Platform-specific + /// /// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for /// example when the scaling of the page has changed. This should be treated as an implementation @@ -142,10 +159,12 @@ pub enum ControlFlow { /// arrives or the given time is reached. /// /// Useful for implementing efficient timers. Applications which want to render at the display's - /// native refresh rate should instead use `Poll` and the VSync functionality of a graphics API + /// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API /// to reduce odds of missed frames. + /// + /// [`Poll`]: Self::Poll WaitUntil(Instant), - /// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set, + /// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set, /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will /// result in the `control_flow` parameter being reset to `ExitWithCode`. /// @@ -154,11 +173,12 @@ pub enum ControlFlow { /// /// ## Platform-specific /// - /// - **Android / iOS / WASM**: The supplied exit code is unused. - /// - **Unix**: On most Unix-like platforms, only the 8 least significant bits will be used, + /// - **Android / iOS / WASM:** The supplied exit code is unused. + /// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used, /// which can cause surprises with negative exit values (`-42` would end up as `214`). See /// [`std::process::exit`]. /// + /// [`LoopDestroyed`]: Event::LoopDestroyed /// [`Exit`]: ControlFlow::Exit ExitWithCode(i32), } @@ -166,41 +186,41 @@ pub enum ControlFlow { impl ControlFlow { /// Alias for [`ExitWithCode`]`(0)`. /// - /// [`ExitWithCode`]: ControlFlow::ExitWithCode + /// [`ExitWithCode`]: Self::ExitWithCode #[allow(non_upper_case_globals)] pub const Exit: Self = Self::ExitWithCode(0); /// Sets this to [`Poll`]. /// - /// [`Poll`]: ControlFlow::Poll + /// [`Poll`]: Self::Poll pub fn set_poll(&mut self) { *self = Self::Poll; } /// Sets this to [`Wait`]. /// - /// [`Wait`]: ControlFlow::Wait + /// [`Wait`]: Self::Wait pub fn set_wait(&mut self) { *self = Self::Wait; } /// Sets this to [`WaitUntil`]`(instant)`. /// - /// [`WaitUntil`]: ControlFlow::WaitUntil + /// [`WaitUntil`]: Self::WaitUntil pub fn set_wait_until(&mut self, instant: Instant) { *self = Self::WaitUntil(instant); } /// Sets this to [`ExitWithCode`]`(code)`. /// - /// [`ExitWithCode`]: ControlFlow::ExitWithCode + /// [`ExitWithCode`]: Self::ExitWithCode pub fn set_exit_with_code(&mut self, code: i32) { *self = Self::ExitWithCode(code); } /// Sets this to [`Exit`]. /// - /// [`Exit`]: ControlFlow::Exit + /// [`Exit`]: Self::Exit pub fn set_exit(&mut self) { *self = Self::Exit; } @@ -208,19 +228,27 @@ impl ControlFlow { impl Default for ControlFlow { #[inline(always)] - fn default() -> ControlFlow { - ControlFlow::Poll + fn default() -> Self { + Self::Poll } } impl EventLoop<()> { - /// Alias for `EventLoopBuilder::new().build()`. + /// Alias for [`EventLoopBuilder::new().build()`]. + /// + /// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build #[inline] pub fn new() -> EventLoop<()> { EventLoopBuilder::new().build() } } +impl Default for EventLoop<()> { + fn default() -> Self { + Self::new() + } +} + impl EventLoop { #[deprecated = "Use `EventLoopBuilder::::with_user_event().build()` instead."] pub fn with_user_event() -> EventLoop { @@ -238,7 +266,7 @@ impl EventLoop { /// /// ## Platform-specific /// - /// - **X11 / Wayland**: The program terminates with exit code 1 if the display server + /// - **X11 / Wayland:** The program terminates with exit code 1 if the display server /// disconnects. /// /// [`ControlFlow`]: crate::event_loop::ControlFlow @@ -250,7 +278,7 @@ impl EventLoop { self.event_loop.run(event_handler) } - /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. + /// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop. pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy(), @@ -286,9 +314,38 @@ impl EventLoopWindowTarget { pub fn primary_monitor(&self) -> Option { self.p.primary_monitor() } + + /// Change [`DeviceEvent`] filter mode. + /// + /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit + /// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing + /// this filter at runtime to explicitly capture them again. + /// + /// ## Platform-specific + /// + /// - **Wayland / Windows / macOS / iOS / Android / Web:** Unsupported. + /// + /// [`DeviceEvent`]: crate::event::DeviceEvent + pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + self.p.set_device_event_filter(_filter); + } +} + +unsafe impl HasRawDisplayHandle for EventLoopWindowTarget { + /// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop. + fn raw_display_handle(&self) -> RawDisplayHandle { + self.p.raw_display_handle() + } } -/// Used to send custom events to `EventLoop`. +/// Used to send custom events to [`EventLoop`]. pub struct EventLoopProxy { event_loop_proxy: platform_impl::EventLoopProxy, } @@ -302,11 +359,13 @@ impl Clone for EventLoopProxy { } impl EventLoopProxy { - /// 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 /// function. /// - /// Returns an `Err` if the associated `EventLoop` no longer exists. + /// Returns an `Err` if the associated [`EventLoop`] no longer exists. + /// + /// [`UserEvent(event)`]: Event::UserEvent pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.event_loop_proxy.send_event(event) } @@ -318,8 +377,10 @@ impl fmt::Debug for EventLoopProxy { } } -/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that -/// no longer exists. Contains the original event given to `send_event`. +/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that +/// no longer exists. +/// +/// Contains the original event given to [`EventLoopProxy::send_event`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct EventLoopClosed(pub T); @@ -330,3 +391,20 @@ impl fmt::Display for EventLoopClosed { } impl error::Error for EventLoopClosed {} + +/// Filter controlling the propagation of device events. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum DeviceEventFilter { + /// Always filter out device events. + Always, + /// Filter out device events while the window is not focused. + Unfocused, + /// Report all device events regardless of window focus. + Never, +} + +impl Default for DeviceEventFilter { + fn default() -> Self { + Self::Unfocused + } +} diff --git a/src/icon.rs b/src/icon.rs index 418ea03afa..982e5f5d81 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -13,7 +13,7 @@ pub(crate) struct Pixel { pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); #[derive(Debug)] -/// An error produced when using `Icon::from_rgba` with invalid arguments. +/// An error produced when using [`Icon::from_rgba`] with invalid arguments. pub enum BadIcon { /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be /// safely interpreted as 32bpp RGBA pixels. @@ -73,10 +73,6 @@ mod constructors { use super::*; impl RgbaIcon { - /// Creates an `Icon` from 32bpp RGBA data. - /// - /// The length of `rgba` must be divisible by 4, and `width * height` must equal - /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { if rgba.len() % PIXEL_SIZE != 0 { return Err(BadIcon::ByteCountNotDivisibleBy4 { @@ -123,7 +119,7 @@ impl fmt::Debug for Icon { } impl Icon { - /// Creates an `Icon` from 32bpp RGBA data. + /// Creates an icon from 32bpp RGBA data. /// /// The length of `rgba` must be divisible by 4, and `width * height` must equal /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. diff --git a/src/lib.rs b/src/lib.rs index a74586f850..4871bb3c58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,9 +98,9 @@ //! # Drawing on the window //! //! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to -//! retrieve the raw handle of the window (see the [`platform`] module and/or the -//! [`raw_window_handle`] method), which in turn allows you to create an -//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. +//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the +//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows +//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! //! Note that many platforms will display garbage data in the window's client area if the //! application doesn't render anything to the window by the time the desktop compositor is ready to @@ -129,13 +129,14 @@ //! [`LoopDestroyed`]: event::Event::LoopDestroyed //! [`platform`]: platform //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle +//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle #![deny(rust_2018_idioms)] #![deny(rustdoc::broken_intra_doc_links)] +#![deny(clippy::all)] +#![cfg_attr(feature = "cargo-clippy", deny(warnings))] +#![allow(clippy::missing_safety_doc)] -#[allow(unused_imports)] -#[macro_use] -extern crate lazy_static; #[allow(unused_imports)] #[macro_use] extern crate log; diff --git a/src/monitor.rs b/src/monitor.rs index 5cf680ff21..0ee4817737 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,14 +1,10 @@ //! Types useful for interacting with a user's monitors. //! -//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] -//! type. This is retrieved from one of the following methods, which return an iterator of -//! [`MonitorHandle`][monitor_handle]: -//! - [`EventLoopWindowTarget::available_monitors`][loop_get] -//! - [`Window::available_monitors`][window_get]. -//! -//! [monitor_handle]: crate::monitor::MonitorHandle -//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors -//! [window_get]: crate::window::Window::available_monitors +//! If you want to get basic information about a monitor, you can use the +//! [`MonitorHandle`] type. This is retrieved from one of the following +//! methods, which return an iterator of [`MonitorHandle`]: +//! - [`EventLoopWindowTarget::available_monitors`](crate::event_loop::EventLoopWindowTarget::available_monitors). +//! - [`Window::available_monitors`](crate::window::Window::available_monitors). use crate::{ dpi::{PhysicalPosition, PhysicalSize}, platform_impl, @@ -16,10 +12,7 @@ use crate::{ /// Describes a fullscreen video mode of a monitor. /// -/// Can be acquired with: -/// - [`MonitorHandle::video_modes`][monitor_get]. -/// -/// [monitor_get]: crate::monitor::MonitorHandle::video_modes +/// Can be acquired with [`MonitorHandle::video_modes`]. #[derive(Clone, PartialEq, Eq, Hash)] pub struct VideoMode { pub(crate) video_mode: platform_impl::VideoMode, @@ -46,8 +39,8 @@ impl Ord for VideoMode { self.monitor().cmp(&other.monitor()).then( size.cmp(&other_size) .then( - self.refresh_rate() - .cmp(&other.refresh_rate()) + self.refresh_rate_millihertz() + .cmp(&other.refresh_rate_millihertz()) .then(self.bit_depth().cmp(&other.bit_depth())), ) .reverse(), @@ -75,12 +68,10 @@ impl VideoMode { self.video_mode.bit_depth() } - /// Returns the refresh rate of this video mode. **Note**: the returned - /// refresh rate is an integer approximation, and you shouldn't rely on this - /// value to be exact. + /// Returns the refresh rate of this video mode in mHz. #[inline] - pub fn refresh_rate(&self) -> u16 { - self.video_mode.refresh_rate() + pub fn refresh_rate_millihertz(&self) -> u32 { + self.video_mode.refresh_rate_millihertz() } /// Returns the monitor that this video mode is valid for. Each monitor has @@ -95,10 +86,10 @@ impl std::fmt::Display for VideoMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{}x{} @ {} Hz ({} bpp)", + "{}x{} @ {} mHz ({} bpp)", self.size().width, self.size().height, - self.refresh_rate(), + self.refresh_rate_millihertz(), self.bit_depth() ) } @@ -148,6 +139,15 @@ impl MonitorHandle { self.inner.position() } + /// The monitor refresh rate used by the system. + /// + /// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to + /// enter fullscreen should be used instead. + #[inline] + pub fn refresh_rate_millihertz(&self) -> Option { + self.inner.refresh_rate_millihertz() + } + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. /// /// See the [`dpi`](crate::dpi) module for more information. diff --git a/src/platform/android.rs b/src/platform/android.rs index b4e9917642..af15f3efd9 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -7,15 +7,15 @@ use crate::{ use ndk::configuration::Configuration; use ndk_glue::Rect; -/// Additional methods on `EventLoop` that are specific to Android. +/// Additional methods on [`EventLoop`] that are specific to Android. pub trait EventLoopExtAndroid {} impl EventLoopExtAndroid for EventLoop {} -/// Additional methods on `EventLoopWindowTarget` that are specific to Android. +/// Additional methods on [`EventLoopWindowTarget`] that are specific to Android. pub trait EventLoopWindowTargetExtAndroid {} -/// Additional methods on `Window` that are specific to Android. +/// Additional methods on [`Window`] that are specific to Android. pub trait WindowExtAndroid { fn content_rect(&self) -> Rect; @@ -34,7 +34,7 @@ impl WindowExtAndroid for Window { impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} -/// Additional methods on `WindowBuilder` that are specific to Android. +/// Additional methods on [`WindowBuilder`] that are specific to Android. pub trait WindowBuilderExtAndroid {} impl WindowBuilderExtAndroid for WindowBuilder {} diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 1ddab448a9..10997e5247 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -9,16 +9,16 @@ use crate::{ window::{Window, WindowBuilder}, }; -/// Additional methods on `Window` that are specific to MacOS. +/// Additional methods on [`Window`] that are specific to MacOS. pub trait WindowExtMacOS { /// Returns a pointer to the cocoa `NSWindow` that is used by this window. /// - /// The pointer will become invalid when the `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. fn ns_window(&self) -> *mut c_void; /// Returns a pointer to the cocoa `NSView` 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. fn ns_view(&self) -> *mut c_void; /// Returns whether or not the window is in simple fullscreen mode. @@ -89,16 +89,14 @@ impl Default for ActivationPolicy { } } -/// Additional methods on `WindowBuilder` that are specific to MacOS. +/// Additional methods on [`WindowBuilder`] that are specific to MacOS. /// -/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method -/// on the base `WindowBuilder`: -/// -/// - `with_titlebar_transparent` -/// - `with_title_hidden` -/// - `with_titlebar_hidden` -/// - `with_titlebar_buttons_hidden` -/// - `with_fullsize_content_view` +/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method: +/// - `with_titlebar_transparent` +/// - `with_title_hidden` +/// - `with_titlebar_hidden` +/// - `with_titlebar_buttons_hidden` +/// - `with_fullsize_content_view` pub trait WindowBuilderExtMacOS { /// Enables click-and-drag behavior for the entire window, not just the titlebar. fn with_movable_by_window_background(self, movable_by_window_background: bool) @@ -238,7 +236,7 @@ impl EventLoopBuilderExtMacOS for EventLoopBuilder { } } -/// Additional methods on `MonitorHandle` that are specific to MacOS. +/// Additional methods on [`MonitorHandle`] that are specific to MacOS. pub trait MonitorHandleExtMacOS { /// Returns the identifier of the monitor for Cocoa. fn native_id(&self) -> u32; @@ -257,7 +255,7 @@ impl MonitorHandleExtMacOS for MonitorHandle { } } -/// Additional methods on `EventLoopWindowTarget` that are specific to macOS. +/// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS. pub trait EventLoopWindowTargetExtMacOS { /// Hide the entire application. In most applications this is typically triggered with Command-H. fn hide_application(&self); diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 1ee5fce2bb..dde4c079cf 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -19,7 +19,7 @@ pub mod android; pub mod ios; pub mod macos; pub mod unix; +pub mod web; pub mod windows; pub mod run_return; -pub mod web; diff --git a/src/platform/run_return.rs b/src/platform/run_return.rs index 1c2fa62326..5013823bc1 100644 --- a/src/platform/run_return.rs +++ b/src/platform/run_return.rs @@ -14,17 +14,18 @@ use crate::{ event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, }; -/// Additional methods on `EventLoop` to return control flow to the caller. +/// Additional methods on [`EventLoop`] to return control flow to the caller. pub trait EventLoopExtRunReturn { - /// A type provided by the user that can be passed through `Event::UserEvent`. + /// A type provided by the user that can be passed through [`Event::UserEvent`]. type UserEvent; /// Initializes the `winit` event loop. /// - /// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns - /// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`. + /// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures + /// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`]. /// /// # Caveats + /// /// Despite its appearance at first glance, this is *not* a perfect replacement for /// `poll_events`. For example, this function will not return on Windows or macOS while a /// window is getting resized, resulting in all application logic outside of the @@ -36,7 +37,7 @@ pub trait EventLoopExtRunReturn { /// /// ## Platform-specific /// - /// - **Unix-alikes** (**X11** or **Wayland**): This function returns `1` upon disconnection from + /// - **X11 / Wayland:** This function returns `1` upon disconnection from /// the display server. fn run_return(&mut self, event_handler: F) -> i32 where diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 81b06ad07b..1d33e846bd 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -19,7 +19,7 @@ use crate::{ #[cfg(feature = "x11")] use crate::dpi::Size; #[cfg(feature = "x11")] -use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection}; +use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS}; use crate::platform_impl::{ ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget, Window as LinuxWindow, @@ -35,13 +35,38 @@ pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupport #[cfg(feature = "wayland")] pub use crate::window::Theme; -/// Additional methods on `EventLoopWindowTarget` that are specific to Unix. +/// The first argument in the provided hook will be the pointer to `XDisplay` +/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an +/// indicator whether the error was handled by the callback. +/// +/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent +#[cfg(feature = "x11")] +pub type XlibErrorHook = + Box bool + Send + Sync>; + +/// Hook to winit's xlib error handling callback. +/// +/// This method is provided as a safe way to handle the errors comming from X11 when using xlib +/// in external crates, like glutin for GLX access. Trying to handle errors by speculating with +/// `XSetErrorHandler` is [`unsafe`]. +/// +/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml +#[inline] +#[cfg(feature = "x11")] +pub fn register_xlib_error_hook(hook: XlibErrorHook) { + // Append new hook. + unsafe { + XLIB_ERROR_HOOKS.lock().push(hook); + } +} + +/// Additional methods on [`EventLoopWindowTarget`] that are specific to Unix. pub trait EventLoopWindowTargetExtUnix { - /// True if the `EventLoopWindowTarget` uses Wayland. + /// True if the [`EventLoopWindowTarget`] uses Wayland. #[cfg(feature = "wayland")] fn is_wayland(&self) -> bool; - /// True if the `EventLoopWindowTarget` uses X11. + /// True if the [`EventLoopWindowTarget`] uses X11. #[cfg(feature = "x11")] fn is_x11(&self) -> bool; @@ -50,11 +75,13 @@ pub trait EventLoopWindowTargetExtUnix { fn xlib_xconnection(&self) -> Option>; /// Returns a pointer to the `wl_display` object of wayland that is used by this - /// `EventLoopWindowTarget`. + /// [`EventLoopWindowTarget`]. + /// + /// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example). /// - /// 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. /// - /// The pointer will become invalid when the winit `EventLoop` is destroyed. + /// [`EventLoop`]: crate::event_loop::EventLoop #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void>; } @@ -134,9 +161,9 @@ impl EventLoopBuilderExtUnix for EventLoopBuilder { } } -/// Additional methods on `Window` that are specific to Unix. +/// Additional methods on [`Window`] that are specific to Unix. pub trait WindowExtUnix { - /// Returns the ID of the `Window` xlib object that is used by this window. + /// Returns the ID of the [`Window`] xlib object that is used by this window. /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). #[cfg(feature = "x11")] @@ -146,7 +173,7 @@ pub trait WindowExtUnix { /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// - /// The pointer will become invalid when the glutin `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. #[cfg(feature = "x11")] fn xlib_display(&self) -> Option<*mut raw::c_void>; @@ -161,7 +188,7 @@ pub trait WindowExtUnix { /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// - /// The pointer will become invalid when the glutin `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void>; @@ -169,7 +196,7 @@ pub trait WindowExtUnix { /// /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// - /// The pointer will become invalid when the glutin `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. #[cfg(feature = "wayland")] fn wayland_surface(&self) -> Option<*mut raw::c_void>; @@ -177,7 +204,7 @@ pub trait WindowExtUnix { /// /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// - /// The pointer will become invalid when the glutin `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void>; @@ -193,7 +220,7 @@ pub trait WindowExtUnix { /// It is a remnant of a previous implementation detail for the /// wayland backend, and is no longer relevant. /// - /// Always return true. + /// Always return `true`. #[deprecated] fn is_ready(&self) -> bool; } @@ -272,10 +299,11 @@ impl WindowExtUnix for Window { #[inline] #[cfg(feature = "wayland")] fn wayland_set_csd_theme(&self, theme: Theme) { + #[allow(clippy::single_match)] match self.window { LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme), #[cfg(feature = "x11")] - _ => {} + _ => (), } } @@ -285,7 +313,7 @@ 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 { #[cfg(feature = "x11")] fn with_x11_visual(self, visual_infos: *const T) -> Self; diff --git a/src/platform/web.rs b/src/platform/web.rs index 210f87b9d2..82e42db336 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -1,10 +1,14 @@ #![cfg(target_arch = "wasm32")] //! The web target does not automatically insert the canvas element object into the web page, to -//! allow end users to determine how the page should be laid out. Use the `WindowExtWebSys` trait -//! to retrieve the canvas from the Window. Alternatively, use the `WindowBuilderExtWebSys` trait +//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait +//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait //! to provide your own canvas. +use crate::event::Event; +use crate::event_loop::ControlFlow; +use crate::event_loop::EventLoop; +use crate::event_loop::EventLoopWindowTarget; use crate::window::WindowBuilder; use web_sys::HtmlCanvasElement; @@ -18,6 +22,17 @@ pub trait WindowExtWebSys { pub trait WindowBuilderExtWebSys { fn with_canvas(self, canvas: Option) -> Self; + + /// Whether `event.preventDefault` should be automatically called to prevent event propagation + /// when appropriate. + /// + /// For example, mouse wheel events are only handled by the canvas by default. This avoids + /// the default behavior of scrolling the page. + fn with_prevent_default(self, prevent_default: bool) -> Self; + + /// Whether the canvas should be focusable using the tab key. This is necessary to capture + /// canvas keyboard events. + fn with_focusable(self, focusable: bool) -> Self; } impl WindowBuilderExtWebSys for WindowBuilder { @@ -26,4 +41,51 @@ impl WindowBuilderExtWebSys for WindowBuilder { self } + + fn with_prevent_default(mut self, prevent_default: bool) -> Self { + self.platform_specific.prevent_default = prevent_default; + + self + } + + fn with_focusable(mut self, focusable: bool) -> Self { + self.platform_specific.focusable = focusable; + + self + } +} + +/// Additional methods on `EventLoop` that are specific to the web. +pub trait EventLoopExtWebSys { + /// A type provided by the user that can be passed through `Event::UserEvent`. + type UserEvent; + + /// Initializes the winit event loop. + /// + /// Unlike `run`, this returns immediately, and doesn't throw an exception in order to + /// satisfy its `!` return type. + fn spawn(self, event_handler: F) + where + F: 'static + + FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ); +} + +impl EventLoopExtWebSys for EventLoop { + type UserEvent = T; + + fn spawn(self, event_handler: F) + where + F: 'static + + FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ), + { + self.event_loop.spawn(event_handler) + } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 27d9984b1d..9c275e6ed8 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -1,38 +1,45 @@ #![cfg(target_os = "android")] -use crate::{ - dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - error, - event::{self, VirtualKeyCode}, - event_loop::{self, ControlFlow}, - monitor, window, +use std::{ + collections::VecDeque, + sync::{mpsc, RwLock}, + time::{Duration, Instant}, }; + use ndk::{ configuration::Configuration, event::{InputEvent, KeyAction, Keycode, MotionAction}, looper::{ForeignLooper, Poll, ThreadLooper}, + native_window::NativeWindow, }; -use ndk_glue::{Event, Rect}; -use raw_window_handle::{AndroidNdkHandle, RawWindowHandle}; -use std::{ - collections::VecDeque, - sync::{Arc, Mutex, RwLock}, - time::{Duration, Instant}, +use ndk_glue::{Event, LockReadGuard, Rect}; +use once_cell::sync::Lazy; +use raw_window_handle::{ + AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, +}; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error, + event::{self, VirtualKeyCode}, + event_loop::{self, ControlFlow}, + monitor, + window::{self, CursorGrabMode}, }; -lazy_static! { - static ref CONFIG: RwLock = RwLock::new(Configuration::from_asset_manager( +static CONFIG: Lazy> = Lazy::new(|| { + RwLock::new(Configuration::from_asset_manager( #[allow(deprecated)] // TODO: rust-windowing/winit#2196 - &ndk_glue::native_activity().asset_manager() - )); - // If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event - // contained in the `Option`. The event is moved outside of the `Option` replacing it with a - // `None`. - // - // This allows us to inject event into the event loop without going through `ndk-glue` and - // calling unsafe function that should only be called by Android. - static ref INTERNAL_EVENT: RwLock> = RwLock::new(None); -} + &ndk_glue::native_activity().asset_manager(), + )) +}); +// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event +// contained in the `Option`. The event is moved outside of the `Option` replacing it with a +// `None`. +// +// This allows us to inject event into the event loop without going through `ndk-glue` and +// calling unsafe function that should only be called by Android. +static INTERNAL_EVENT: Lazy>> = Lazy::new(|| RwLock::new(None)); enum InternalEvent { RedrawRequested, @@ -230,14 +237,16 @@ fn poll(poll: Poll) -> Option { pub struct EventLoop { window_target: event_loop::EventLoopWindowTarget, - user_queue: Arc>>, + user_events_sender: mpsc::Sender, + user_events_receiver: mpsc::Receiver, first_event: Option, start_cause: event::StartCause, looper: ThreadLooper, running: bool, + window_lock: Option>, } -#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} macro_rules! call_event_handler { @@ -252,6 +261,7 @@ macro_rules! call_event_handler { impl EventLoop { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { + let (user_events_sender, user_events_receiver) = mpsc::channel(); Self { window_target: event_loop::EventLoopWindowTarget { p: EventLoopWindowTarget { @@ -259,11 +269,13 @@ impl EventLoop { }, _marker: std::marker::PhantomData, }, - user_queue: Default::default(), + user_events_sender, + user_events_receiver, first_event: None, start_cause: event::StartCause::Init, looper: ThreadLooper::for_thread().unwrap(), running: false, + window_lock: None, } } @@ -296,22 +308,42 @@ impl EventLoop { match self.first_event.take() { Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { Event::WindowCreated => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Resumed - ); + // Acquire a lock on the window to prevent Android from destroying + // it until we've notified and waited for the user in Event::Suspended. + // WARNING: ndk-glue is inherently racy (https://github.com/rust-windowing/winit/issues/2293) + // and may have already received onNativeWindowDestroyed while this thread hasn't yet processed + // the event, and would see a `None` lock+window in that case. + if let Some(next_window_lock) = ndk_glue::native_window() { + assert!( + self.window_lock.replace(next_window_lock).is_none(), + "Received `Event::WindowCreated` while we were already holding a lock" + ); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::Resumed + ); + } else { + warn!("Received `Event::WindowCreated` while `ndk_glue::native_window()` provides no window"); + } } Event::WindowResized => resized = true, Event::WindowRedrawNeeded => redraw = true, Event::WindowDestroyed => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Suspended - ); + // Release the lock, allowing Android to clean up this surface + // WARNING: See above - if ndk-glue is racy, this event may be called + // without having a `self.window_lock` in place. + if self.window_lock.take().is_some() { + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::Suspended + ); + } else { + warn!("Received `Event::WindowDestroyed` while we were not holding a window lock"); + } } Event::Pause => self.running = false, Event::Resume => self.running = true, @@ -365,7 +397,7 @@ impl EventLoop { }, Some(EventSource::InputQueue) => { if let Some(input_queue) = ndk_glue::input_queue().as_ref() { - while let Some(event) = input_queue.get_event() { + while let Some(event) = input_queue.get_event().expect("get_event") { if let Some(event) = input_queue.pre_dispatch(event) { let mut handled = true; let window_id = window::WindowId(WindowId); @@ -466,8 +498,9 @@ impl EventLoop { } } Some(EventSource::User) => { - let mut user_queue = self.user_queue.lock().unwrap(); - while let Some(event) = user_queue.pop_front() { + // try_recv only errors when empty (expected) or disconnect. But because Self + // contains a Sender it will never disconnect, so no error handling need. + while let Ok(event) = self.user_events_receiver.try_recv() { call_event_handler!( event_handler, self.window_target(), @@ -568,20 +601,22 @@ impl EventLoop { pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { - queue: self.user_queue.clone(), + user_events_sender: self.user_events_sender.clone(), looper: ForeignLooper::for_thread().expect("called from event loop thread"), } } } pub struct EventLoopProxy { - queue: Arc>>, + user_events_sender: mpsc::Sender, looper: ForeignLooper, } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { - self.queue.lock().unwrap().push_back(event); + self.user_events_sender + .send(event) + .map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?; self.looper.wake(); Ok(()) } @@ -590,7 +625,7 @@ impl EventLoopProxy { impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { - queue: self.queue.clone(), + user_events_sender: self.user_events_sender.clone(), looper: self.looper.clone(), } } @@ -612,6 +647,10 @@ impl EventLoopWindowTarget { v.push_back(MonitorHandle); v } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Android(AndroidDisplayHandle::empty()) + } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -623,6 +662,18 @@ impl WindowId { } } +impl From for u64 { + fn from(_: WindowId) -> Self { + 0 + } +} + +impl From for WindowId { + fn from(_: u64) -> Self { + Self + } +} + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId; @@ -638,7 +689,7 @@ pub struct PlatformSpecificWindowBuilderAttributes; pub struct Window; impl Window { - pub fn new( + pub(crate) fn new( _el: &EventLoopWindowTarget, _window_attrs: window::WindowAttributes, _: PlatformSpecificWindowBuilderAttributes, @@ -762,7 +813,7 @@ impl Window { )) } - pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> { + pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( error::NotSupportedError::new(), )) @@ -783,13 +834,15 @@ impl Window { } pub fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = AndroidNdkHandle::empty(); - if let Some(native_window) = ndk_glue::native_window().as_ref() { - handle.a_native_window = unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ } + if let Some(native_window) = ndk_glue::native_window() { + native_window.raw_window_handle() } else { panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); - }; - RawWindowHandle::AndroidNdk(handle) + } + } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Android(AndroidDisplayHandle::empty()) } pub fn config(&self) -> Configuration { @@ -843,20 +896,23 @@ impl MonitorHandle { .unwrap_or(1.0) } + pub fn refresh_rate_millihertz(&self) -> Option { + // FIXME no way to get real refresh rate for now. + None + } + pub fn video_modes(&self) -> impl Iterator { let size = self.size().into(); - let mut v = Vec::new(); // FIXME this is not the real refresh rate - // (it is guarunteed to support 32 bit color though) - v.push(monitor::VideoMode { + // (it is guaranteed to support 32 bit color though) + std::iter::once(monitor::VideoMode { video_mode: VideoMode { size, bit_depth: 32, - refresh_rate: 60, + refresh_rate_millihertz: 60000, monitor: self.clone(), }, - }); - v.into_iter() + }) } } @@ -864,7 +920,7 @@ impl MonitorHandle { pub struct VideoMode { size: (u32, u32), bit_depth: u16, - refresh_rate: u16, + refresh_rate_millihertz: u32, monitor: MonitorHandle, } @@ -877,8 +933,8 @@ impl VideoMode { self.bit_depth } - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate + pub fn refresh_rate_millihertz(&self) -> u32 { + self.refresh_rate_millihertz } pub fn monitor(&self) -> monitor::MonitorHandle { diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 6d1d206942..0586084476 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -10,6 +10,7 @@ use std::{ }; use objc::runtime::{BOOL, YES}; +use once_cell::sync::Lazy; use crate::{ dpi::LogicalSize, @@ -52,11 +53,7 @@ enum UserCallbackTransitionResult<'a> { impl Event<'static, Never> { fn is_redraw(&self) -> bool { - if let Event::RedrawRequested(_) = self { - true - } else { - false - } + matches!(self, Event::RedrawRequested(_)) } } @@ -119,7 +116,7 @@ impl Drop for AppState { } => { for &mut window in queued_windows { unsafe { - let () = msg_send![window, release]; + let _: () = msg_send![window, release]; } } } @@ -200,10 +197,10 @@ impl AppState { } fn has_launched(&self) -> bool { - match self.state() { - &AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false, - _ => true, - } + !matches!( + self.state(), + AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. } + ) } fn will_launch_transition(&mut self, queued_event_handler: Box) { @@ -528,7 +525,9 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) { | &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. - } => drop(queued_gpu_redraws.insert(window)), + } => { + let _ = queued_gpu_redraws.insert(window); + } s @ &mut AppStateImpl::ProcessingRedraws { .. } | s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), @@ -536,6 +535,7 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) { panic!("Attempt to create a `Window` after the app has terminated") } } + drop(this); } @@ -548,7 +548,7 @@ pub unsafe fn will_launch(queued_event_handler: Box) { pub unsafe fn did_finish_launching() { let mut this = AppState::get_mut(); let windows = match this.state_mut() { - AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()), + AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), s => bug!("unexpected state {:?}", s), }; @@ -578,15 +578,15 @@ pub unsafe fn did_finish_launching() { // ``` let screen: id = msg_send![window, screen]; let _: id = msg_send![screen, retain]; - let () = msg_send![window, setScreen:0 as id]; - let () = msg_send![window, setScreen: screen]; - let () = msg_send![screen, release]; + let _: () = msg_send![window, setScreen:0 as id]; + let _: () = msg_send![window, setScreen: screen]; + let _: () = msg_send![screen, release]; let controller: id = msg_send![window, rootViewController]; - let () = msg_send![window, setRootViewController:ptr::null::<()>()]; - let () = msg_send![window, setRootViewController: controller]; - let () = msg_send![window, makeKeyAndVisible]; + let _: () = msg_send![window, setRootViewController:ptr::null::<()>()]; + let _: () = msg_send![window, setRootViewController: controller]; + let _: () = msg_send![window, makeKeyAndVisible]; } - let () = msg_send![window, release]; + let _: () = msg_send![window, release]; } let (windows, events) = AppState::get_mut().did_finish_launching_transition(); @@ -603,9 +603,9 @@ pub unsafe fn did_finish_launching() { let count: NSUInteger = msg_send![window, retainCount]; // make sure the window is still referenced if count > 1 { - let () = msg_send![window, makeKeyAndVisible]; + let _: () = msg_send![window, makeKeyAndVisible]; } - let () = msg_send![window, release]; + let _: () = msg_send![window, release]; } } @@ -670,7 +670,7 @@ pub unsafe fn handle_nonuser_events>(events &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _, - } => mem::replace(queued_events, Vec::new()), + } => mem::take(queued_events), s => bug!("unexpected state {:?}", s), }; if queued_events.is_empty() { @@ -751,7 +751,7 @@ unsafe fn handle_user_events() { &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _, - } => mem::replace(queued_events, Vec::new()), + } => mem::take(queued_events), s => bug!("unexpected state {:?}", s), }; if queued_events.is_empty() { @@ -793,7 +793,7 @@ pub unsafe fn handle_main_events_cleared() { return; } match this.state_mut() { - &mut AppStateImpl::ProcessingEvents { .. } => {} + AppStateImpl::ProcessingEvents { .. } => {} _ => bug!("`ProcessingRedraws` happened unexpectedly"), }; drop(this); @@ -875,7 +875,7 @@ fn handle_hidpi_proxy( let size = CGSize::new(logical_size); let new_frame: CGRect = CGRect::new(screen_frame.origin, size); unsafe { - let () = msg_send![view, setFrame: new_frame]; + let _: () = msg_send![view, setFrame: new_frame]; } } @@ -990,20 +990,20 @@ macro_rules! os_capabilities { } os_capabilities! { - /// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + /// #[allow(unused)] // error message unused safe_area_err_msg: "-[UIView safeAreaInsets]", safe_area: 11-0, - /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc + /// home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", home_indicator_hidden: 11-0, - /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc + /// defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", defer_system_gestures: 11-0, - /// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc + /// maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", maximum_frames_per_second: 10-3, - /// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc + /// #[allow(unused)] // error message unused force_touch_err_msg: "-[UITouch force]", force_touch: 9-0, @@ -1016,29 +1016,27 @@ impl NSOperatingSystemVersion { } pub fn os_capabilities() -> OSCapabilities { - lazy_static! { - static ref OS_CAPABILITIES: OSCapabilities = { - let version: NSOperatingSystemVersion = unsafe { - let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; - let atleast_ios_8: BOOL = msg_send![ - process_info, - respondsToSelector: sel!(operatingSystemVersion) - ]; - // winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions. - // Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support - // debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS - // simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7 - // has been tested to not even run on macOS 10.15 - Xcode 8 might? - // - // The minimum required iOS version is likely to grow in the future. - assert!( - atleast_ios_8 == YES, - "`winit` requires iOS version 8 or greater" - ); - msg_send![process_info, operatingSystemVersion] - }; - version.into() + static OS_CAPABILITIES: Lazy = Lazy::new(|| { + let version: NSOperatingSystemVersion = unsafe { + let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; + let atleast_ios_8: BOOL = msg_send![ + process_info, + respondsToSelector: sel!(operatingSystemVersion) + ]; + // winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions. + // Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support + // debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS + // simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7 + // has been tested to not even run on macOS 10.15 - Xcode 8 might? + // + // The minimum required iOS version is likely to grow in the future. + assert!( + atleast_ios_8 == YES, + "`winit` requires iOS version 8 or greater" + ); + msg_send![process_info, operatingSystemVersion] }; - } + version.into() + }); OS_CAPABILITIES.clone() } diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index feb6b7ea53..eeaf2e6894 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -7,6 +7,8 @@ use std::{ sync::mpsc::{self, Receiver, Sender}, }; +use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle}; + use crate::{ dpi::LogicalSize, event::Event, @@ -63,13 +65,17 @@ impl EventLoopWindowTarget { Some(RootMonitorHandle { inner: monitor }) } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::UiKit(UiKitDisplayHandle::empty()) + } } pub struct EventLoop { window_target: RootEventLoopWindowTarget, } -#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} impl EventLoop { diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 726723f30b..7f560514d1 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -88,7 +88,7 @@ pub enum UITouchPhase { Cancelled, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] #[allow(dead_code)] #[repr(isize)] pub enum UIForceTouchCapability { @@ -97,7 +97,7 @@ pub enum UIForceTouchCapability { Available, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] #[allow(dead_code)] #[repr(isize)] pub enum UITouchType { @@ -144,10 +144,9 @@ impl From for UIUserInterfaceIdiom { } } } - -impl Into for UIUserInterfaceIdiom { - fn into(self) -> Idiom { - match self { +impl From for Idiom { + fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom { + match ui_idiom { UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified, UIUserInterfaceIdiom::Phone => Idiom::Phone, UIUserInterfaceIdiom::Pad => Idiom::Pad, @@ -230,9 +229,9 @@ impl From for UIRectEdge { } } -impl Into for UIRectEdge { - fn into(self) -> ScreenEdge { - let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`"); +impl From for ScreenEdge { + fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge { + let bits: u8 = ui_rect_edge.0.try_into().expect("invalid `UIRectEdge`"); ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`") } } diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index 53c230f3f0..37677918e5 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -56,6 +56,7 @@ //! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed. #![cfg(target_os = "ios")] +#![allow(clippy::let_unit_value)] // TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be // worked around in the future by using GCD (grand central dispatch) and/or caching of values like @@ -108,9 +109,7 @@ unsafe impl Sync for DeviceId {} pub enum OsError {} impl fmt::Display for OsError { - fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - _ => unreachable!(), - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "os error") } } diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 4ba3a3dbb3..42516cb0c4 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -17,7 +17,7 @@ use crate::{ pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, + pub(crate) refresh_rate_millihertz: u32, pub(crate) screen_mode: NativeDisplayMode, pub(crate) monitor: MonitorHandle, } @@ -30,7 +30,7 @@ unsafe impl Send for NativeDisplayMode {} impl Drop for NativeDisplayMode { fn drop(&mut self) { unsafe { - let () = msg_send![self.0, release]; + let _: () = msg_send![self.0, release]; } } } @@ -49,7 +49,7 @@ impl Clone for VideoMode { VideoMode { size: self.size, bit_depth: self.bit_depth, - refresh_rate: self.refresh_rate, + refresh_rate_millihertz: self.refresh_rate_millihertz, screen_mode: self.screen_mode.clone(), monitor: self.monitor.clone(), } @@ -59,30 +59,14 @@ impl Clone for VideoMode { 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 refresh_rate_millihertz = refresh_rate_millihertz(uiscreen); let size: CGSize = msg_send![screen_mode, size]; let screen_mode: id = msg_send![screen_mode, retain]; let screen_mode = NativeDisplayMode(screen_mode); VideoMode { size: (size.width as u32, size.height as u32), bit_depth: 32, - refresh_rate: refresh_rate as u16, + refresh_rate_millihertz, screen_mode, monitor: MonitorHandle::retained_new(uiscreen), } @@ -96,8 +80,8 @@ impl VideoMode { self.bit_depth } - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate + pub fn refresh_rate_millihertz(&self) -> u32 { + self.refresh_rate_millihertz } pub fn monitor(&self) -> RootMonitorHandle { @@ -115,7 +99,7 @@ pub struct Inner { impl Drop for Inner { fn drop(&mut self) { unsafe { - let () = msg_send![self.uiscreen, release]; + let _: () = msg_send![self.uiscreen, release]; } } } @@ -193,7 +177,7 @@ impl MonitorHandle { pub fn retained_new(uiscreen: id) -> MonitorHandle { unsafe { assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS"); - let () = msg_send![uiscreen, retain]; + let _: () = msg_send![uiscreen, retain]; } MonitorHandle { inner: Inner { uiscreen }, @@ -239,6 +223,10 @@ impl Inner { } } + pub fn refresh_rate_millihertz(&self) -> Option { + Some(refresh_rate_millihertz(self.uiscreen)) + } + pub fn video_modes(&self) -> impl Iterator { let mut modes = BTreeSet::new(); unsafe { @@ -257,6 +245,30 @@ impl Inner { } } +fn refresh_rate_millihertz(uiscreen: id) -> u32 { + let refresh_rate_millihertz: NSInteger = unsafe { + let os_capabilities = app_state::os_capabilities(); + if os_capabilities.maximum_frames_per_second { + 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 + } + }; + + refresh_rate_millihertz as u32 * 1000 +} + // MonitorHandleExtIOS impl Inner { pub fn ui_screen(&self) -> id { diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index ccdec6bfbf..b4cb4225f1 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -112,14 +112,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { ))), ); let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![super(object, superclass), drawRect: rect]; + let _: () = msg_send![super(object, superclass), drawRect: rect]; } } extern "C" fn layout_subviews(object: &Object, _: Sel) { unsafe { let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![super(object, superclass), layoutSubviews]; + let _: () = msg_send![super(object, superclass), layoutSubviews]; let window: id = msg_send![object, window]; assert!(!window.is_null()); @@ -133,14 +133,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, } - .to_physical(scale_factor.into()); + .to_physical(scale_factor as f64); // If the app is started in landscape, the view frame and window bounds can be mismatched. // The view frame will be in portrait and the window bounds in landscape. So apply the // window bounds to the view frame to make it consistent. let view_frame: CGRect = msg_send![object, frame]; if view_frame != window_bounds { - let () = msg_send![object, setFrame: window_bounds]; + let _: () = msg_send![object, setFrame: window_bounds]; } app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { @@ -157,7 +157,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { ) { unsafe { let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![ + let _: () = msg_send![ super(object, superclass), setContentScaleFactor: untrusted_scale_factor ]; @@ -180,7 +180,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { && scale_factor > 0.0, "invalid scale_factor set on UIView", ); - let scale_factor: f64 = scale_factor.into(); + let scale_factor = scale_factor as f64; let bounds: CGRect = msg_send![object, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -340,7 +340,7 @@ unsafe fn get_view_controller_class() -> &'static Class { prefers_status_bar_hidden: BOOL, setPrefersStatusBarHidden: |object| { unsafe { - let () = msg_send![object, setNeedsStatusBarAppearanceUpdate]; + let _: () = msg_send![object, setNeedsStatusBarAppearanceUpdate]; } }, prefersStatusBarHidden, @@ -353,7 +353,7 @@ unsafe fn get_view_controller_class() -> &'static Class { OSCapabilities::home_indicator_hidden_err_msg; |object| { unsafe { - let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden]; + let _: () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden]; } }, prefersHomeIndicatorAutoHidden, @@ -363,7 +363,7 @@ unsafe fn get_view_controller_class() -> &'static Class { supported_orientations: UIInterfaceOrientationMask, setSupportedInterfaceOrientations: |object| { unsafe { - let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; + let _: () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; } }, supportedInterfaceOrientations, @@ -376,7 +376,7 @@ unsafe fn get_view_controller_class() -> &'static Class { OSCapabilities::defer_system_gestures_err_msg; |object| { unsafe { - let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; + let _: () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; } }, preferredScreenEdgesDeferringSystemGestures, @@ -398,7 +398,7 @@ unsafe fn get_window_class() -> &'static Class { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(true), })); - let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow]; + let _: () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow]; } } @@ -408,7 +408,7 @@ unsafe fn get_window_class() -> &'static Class { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(false), })); - let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow]; + let _: () = msg_send![super(object, class!(UIWindow)), resignKeyWindow]; } } @@ -429,7 +429,7 @@ unsafe fn get_window_class() -> &'static Class { } // requires main thread -pub unsafe fn create_view( +pub(crate) unsafe fn create_view( _window_attributes: &WindowAttributes, platform_attributes: &PlatformSpecificWindowBuilderAttributes, frame: CGRect, @@ -440,16 +440,16 @@ pub unsafe fn create_view( assert!(!view.is_null(), "Failed to create `UIView` instance"); let view: id = msg_send![view, initWithFrame: frame]; assert!(!view.is_null(), "Failed to initialize `UIView` instance"); - let () = msg_send![view, setMultipleTouchEnabled: YES]; + let _: () = msg_send![view, setMultipleTouchEnabled: YES]; if let Some(scale_factor) = platform_attributes.scale_factor { - let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat]; + let _: () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat]; } view } // requires main thread -pub unsafe fn create_view_controller( +pub(crate) unsafe fn create_view_controller( _window_attributes: &WindowAttributes, platform_attributes: &PlatformSpecificWindowBuilderAttributes, view: id, @@ -484,28 +484,28 @@ pub unsafe fn create_view_controller( let edges: UIRectEdge = platform_attributes .preferred_screen_edges_deferring_system_gestures .into(); - let () = msg_send![ + let _: () = msg_send![ view_controller, setPrefersStatusBarHidden: status_bar_hidden ]; - let () = msg_send![ + let _: () = msg_send![ view_controller, setSupportedInterfaceOrientations: supported_orientations ]; - let () = msg_send![ + let _: () = msg_send![ view_controller, setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden ]; - let () = msg_send![ + let _: () = msg_send![ view_controller, setPreferredScreenEdgesDeferringSystemGestures: edges ]; - let () = msg_send![view_controller, setView: view]; + let _: () = msg_send![view_controller, setView: view]; view_controller } // requires main thread -pub unsafe fn create_window( +pub(crate) unsafe fn create_window( window_attributes: &WindowAttributes, _platform_attributes: &PlatformSpecificWindowBuilderAttributes, frame: CGRect, @@ -520,11 +520,11 @@ pub unsafe fn create_window( !window.is_null(), "Failed to initialize `UIWindow` instance" ); - let () = msg_send![window, setRootViewController: view_controller]; + let _: () = msg_send![window, setRootViewController: view_controller]; match window_attributes.fullscreen { Some(Fullscreen::Exclusive(ref video_mode)) => { let uiscreen = video_mode.monitor().ui_screen() as id; - let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; + let _: () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; msg_send![window, setScreen:video_mode.monitor().ui_screen()] } Some(Fullscreen::Borderless(ref monitor)) => { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 5df7734a34..0a4b81c243 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -1,10 +1,10 @@ -use raw_window_handle::{RawWindowHandle, UiKitHandle}; use std::{ collections::VecDeque, ops::{Deref, DerefMut}, }; use objc::runtime::{Class, Object, BOOL, NO, YES}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle}; use crate::{ dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, @@ -23,7 +23,8 @@ use crate::{ monitor, view, EventLoopWindowTarget, MonitorHandle, }, window::{ - CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, + WindowId as RootWindowId, }, }; @@ -37,9 +38,9 @@ pub struct Inner { impl Drop for Inner { fn drop(&mut self) { unsafe { - let () = msg_send![self.view, release]; - let () = msg_send![self.view_controller, release]; - let () = msg_send![self.window, release]; + let _: () = msg_send![self.view, release]; + let _: () = msg_send![self.view_controller, release]; + let _: () = msg_send![self.window, release]; } } } @@ -52,10 +53,10 @@ impl Inner { pub fn set_visible(&self, visible: bool) { match visible { true => unsafe { - let () = msg_send![self.window, setHidden: NO]; + let _: () = msg_send![self.window, setHidden: NO]; }, false => unsafe { - let () = msg_send![self.window, setHidden: YES]; + let _: () = msg_send![self.window, setHidden: YES]; }, } } @@ -78,7 +79,7 @@ impl Inner { // 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]; + let _: () = msg_send![self.view, setNeedsDisplay]; } } } @@ -119,8 +120,8 @@ impl Inner { }, size: screen_frame.size, }; - let bounds = self.from_screen_space(new_screen_frame); - let () = msg_send![self.window, setBounds: bounds]; + let bounds = self.rect_from_screen_space(new_screen_frame); + let _: () = msg_send![self.window, setBounds: bounds]; } } @@ -184,7 +185,7 @@ impl Inner { Err(ExternalError::NotSupported(NotSupportedError::new())) } - pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { + pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } @@ -218,7 +219,7 @@ impl Inner { let uiscreen = match monitor { Some(Fullscreen::Exclusive(video_mode)) => { let uiscreen = video_mode.video_mode.monitor.ui_screen() as id; - let () = + let _: () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; uiscreen } @@ -234,16 +235,16 @@ impl Inner { // 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 _: () = msg_send![self.window, setScreen: uiscreen]; } let bounds: CGRect = msg_send![uiscreen, bounds]; - let () = msg_send![self.window, setFrame: 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![ + let _: () = msg_send![ uiscreen, setOverscanCompensation: UIScreenOverscanCompensation::None ]; @@ -331,11 +332,15 @@ impl Inner { } pub fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = UiKitHandle::empty(); - handle.ui_window = self.window as _; - handle.ui_view = self.view as _; - handle.ui_view_controller = self.view_controller as _; - RawWindowHandle::UiKit(handle) + let mut window_handle = UiKitWindowHandle::empty(); + window_handle.ui_window = self.window as _; + window_handle.ui_view = self.view as _; + window_handle.ui_view_controller = self.view_controller as _; + RawWindowHandle::UiKit(window_handle) + } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::UiKit(UiKitDisplayHandle::empty()) } } @@ -375,15 +380,15 @@ impl DerefMut for Window { } impl Window { - pub fn new( + pub(crate) fn new( _event_loop: &EventLoopWindowTarget, window_attributes: WindowAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes, ) -> Result { - if let Some(_) = window_attributes.min_inner_size { + if window_attributes.min_inner_size.is_some() { warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); } - if let Some(_) = window_attributes.max_inner_size { + if window_attributes.max_inner_size.is_some() { warn!("`WindowAttributes::max_inner_size` is ignored on iOS"); } if window_attributes.always_on_top { @@ -419,7 +424,7 @@ impl Window { None => screen_bounds, }; - let view = view::create_view(&window_attributes, &platform_attributes, frame.clone()); + let view = view::create_view(&window_attributes, &platform_attributes, frame); let gl_or_metal_backed = { let view_class: id = msg_send![view, class]; @@ -452,7 +457,7 @@ impl Window { // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` // event on window creation if the DPI factor != 1.0 let scale_factor: CGFloat = msg_send![view, contentScaleFactor]; - let scale_factor: f64 = scale_factor.into(); + let scale_factor = scale_factor as f64; if scale_factor != 1.0 { let bounds: CGRect = msg_send![view, bounds]; let screen: id = msg_send![window, screen]; @@ -502,7 +507,7 @@ impl Inner { "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" ); let scale_factor = scale_factor as CGFloat; - let () = msg_send![self.view, setContentScaleFactor: scale_factor]; + let _: () = msg_send![self.view, setContentScaleFactor: scale_factor]; } } @@ -523,7 +528,7 @@ impl Inner { pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { unsafe { let prefers_home_indicator_hidden = if hidden { YES } else { NO }; - let () = msg_send![ + let _: () = msg_send![ self.view_controller, setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden ]; @@ -533,7 +538,7 @@ impl Inner { pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { let edges: UIRectEdge = edges.into(); unsafe { - let () = msg_send![ + let _: () = msg_send![ self.view_controller, setPreferredScreenEdgesDeferringSystemGestures: edges ]; @@ -543,7 +548,7 @@ impl Inner { pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { unsafe { let status_bar_hidden = if hidden { YES } else { NO }; - let () = msg_send![ + let _: () = msg_send![ self.view_controller, setPrefersStatusBarHidden: status_bar_hidden ]; @@ -554,11 +559,11 @@ impl Inner { impl Inner { // requires main thread unsafe fn screen_frame(&self) -> CGRect { - self.to_screen_space(msg_send![self.window, bounds]) + self.rect_to_screen_space(msg_send![self.window, bounds]) } // requires main thread - unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect { + unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect { let screen: id = msg_send![self.window, screen]; if !screen.is_null() { let screen_space: id = msg_send![screen, coordinateSpace]; @@ -569,7 +574,7 @@ impl Inner { } // requires main thread - unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect { + unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect { let screen: id = msg_send![self.window, screen]; if !screen.is_null() { let screen_space: id = msg_send![screen, coordinateSpace]; @@ -594,9 +599,9 @@ impl Inner { height: bounds.size.height - safe_area.top - safe_area.bottom, }, }; - self.to_screen_space(safe_bounds) + self.rect_to_screen_space(safe_bounds) } else { - let screen_frame = self.to_screen_space(bounds); + let screen_frame = self.rect_to_screen_space(bounds); let status_bar_frame: CGRect = { let app: id = msg_send![class!(UIApplication), sharedApplication]; assert!( @@ -640,6 +645,20 @@ impl WindowId { } } +impl From for u64 { + fn from(window_id: WindowId) -> Self { + window_id.window as u64 + } +} + +impl From for WindowId { + fn from(raw_id: u64) -> Self { + Self { + window: raw_id as _, + } + } +} + unsafe impl Send for WindowId {} unsafe impl Sync for WindowId {} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 6552a8227d..f3c9e9c2e8 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -9,8 +9,6 @@ #[cfg(all(not(feature = "x11"), not(feature = "wayland")))] compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); -#[cfg(feature = "wayland")] -use crate::window::Theme; #[cfg(feature = "wayland")] use std::error::Error; @@ -18,22 +16,30 @@ use std::{collections::VecDeque, env, fmt}; #[cfg(feature = "x11")] use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; +#[cfg(feature = "x11")] +use once_cell::sync::Lazy; #[cfg(feature = "x11")] use parking_lot::Mutex; -use raw_window_handle::RawWindowHandle; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; #[cfg(feature = "x11")] pub use self::x11::XNotSupported; #[cfg(feature = "x11")] use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError}; +#[cfg(feature = "x11")] +use crate::platform::unix::XlibErrorHook; +#[cfg(feature = "wayland")] +use crate::window::Theme; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::Event, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + event_loop::{ + ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, + }, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, + window::{CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, }; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; @@ -52,7 +58,7 @@ pub mod x11; /// If this variable is set with any other value, winit will panic. const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; -#[derive(Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum Backend { #[cfg(feature = "x11")] X, @@ -60,22 +66,13 @@ pub(crate) enum Backend { Wayland, } -#[derive(Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) forced_backend: Option, pub(crate) any_thread: bool, } -impl Default for PlatformSpecificEventLoopAttributes { - fn default() -> Self { - Self { - forced_backend: None, - any_thread: false, - } - } -} - -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ApplicationName { pub general: String, pub instance: String, @@ -133,10 +130,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes { } #[cfg(feature = "x11")] -lazy_static! { - pub static ref X11_BACKEND: Mutex, XNotSupported>> = - Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)); -} +pub static X11_BACKEND: Lazy, XNotSupported>>> = + Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))); #[derive(Debug, Clone)] pub enum OsError { @@ -169,19 +164,23 @@ pub enum Window { } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum WindowId { - #[cfg(feature = "x11")] - X(x11::WindowId), - #[cfg(feature = "wayland")] - Wayland(wayland::WindowId), +pub struct WindowId(u64); + +impl From for u64 { + fn from(window_id: WindowId) -> Self { + window_id.0 + } +} + +impl From for WindowId { + fn from(raw_id: u64) -> Self { + Self(raw_id) + } } impl WindowId { pub const unsafe fn dummy() -> Self { - #[cfg(feature = "wayland")] - return WindowId::Wayland(wayland::WindowId::dummy()); - #[cfg(all(not(feature = "wayland"), feature = "x11"))] - return WindowId::X(x11::WindowId::dummy()); + Self(0) } } @@ -259,6 +258,11 @@ impl MonitorHandle { x11_or_wayland!(match self; MonitorHandle(m) => m.position()) } + #[inline] + pub fn refresh_rate_millihertz(&self) -> Option { + x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz()) + } + #[inline] pub fn scale_factor(&self) -> f64 { x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64) @@ -290,8 +294,8 @@ impl VideoMode { } #[inline] - pub fn refresh_rate(&self) -> u16 { - x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate()) + pub fn refresh_rate_millihertz(&self) -> u32 { + x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz()) } #[inline] @@ -302,7 +306,7 @@ impl VideoMode { impl Window { #[inline] - pub fn new( + pub(crate) fn new( window_target: &EventLoopWindowTarget, attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, @@ -321,7 +325,12 @@ impl Window { #[inline] pub fn id(&self) -> WindowId { - x11_or_wayland!(match self; Window(w) => w.id(); as WindowId) + match self { + #[cfg(feature = "wayland")] + Self::Wayland(window) => window.id(), + #[cfg(feature = "x11")] + Self::X(window) => window.id(), + } } #[inline] @@ -395,8 +404,8 @@ impl Window { } #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab)) + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode)) } #[inline] @@ -565,16 +574,22 @@ impl Window { } } + #[inline] pub fn raw_window_handle(&self) -> RawWindowHandle { - match self { - #[cfg(feature = "x11")] - Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()), - #[cfg(feature = "wayland")] - Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()), - } + x11_or_wayland!(match self; Window(window) => window.raw_window_handle()) + } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + x11_or_wayland!(match self; Window(window) => window.raw_display_handle()) } } +/// Hooks for X11 errors. +#[cfg(feature = "x11")] +pub(crate) static mut XLIB_ERROR_HOOKS: Lazy>> = + Lazy::new(|| Mutex::new(Vec::new())); + #[cfg(feature = "x11")] unsafe extern "C" fn x_error_callback( display: *mut x11::ffi::Display, @@ -582,6 +597,12 @@ unsafe extern "C" fn x_error_callback( ) -> c_int { let xconn_lock = X11_BACKEND.lock(); if let Ok(ref xconn) = *xconn_lock { + // Call all the hooks. + let mut error_handled = false; + for hook in XLIB_ERROR_HOOKS.lock().iter() { + error_handled |= hook(display as *mut _, event as *mut _); + } + // `assume_init` is safe here because the array consists of `MaybeUninit` values, // which do not require initialization. let mut buf: [MaybeUninit; 1024] = MaybeUninit::uninit().assume_init(); @@ -600,7 +621,10 @@ unsafe extern "C" fn x_error_callback( minor_code: (*event).minor_code, }; - error!("X11 error: {:#?}", error); + // Don't log error. + if !error_handled { + error!("X11 error: {:#?}", error); + } *xconn.latest_error.lock() = Some(error); } @@ -610,7 +634,7 @@ unsafe extern "C" fn x_error_callback( pub enum EventLoop { #[cfg(feature = "wayland")] - Wayland(wayland::EventLoop), + Wayland(Box>), #[cfg(feature = "x11")] X(x11::EventLoop), } @@ -700,7 +724,7 @@ impl EventLoop { #[cfg(feature = "wayland")] fn new_wayland_any_thread() -> Result, Box> { - wayland::EventLoop::new().map(EventLoop::Wayland) + wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp))) } #[cfg(feature = "x11")] @@ -732,7 +756,7 @@ impl EventLoop { } pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { - x11_or_wayland!(match self; EventLoop(evl) => evl.window_target()) + x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) } } @@ -793,6 +817,20 @@ impl EventLoopWindowTarget { } } } + + #[inline] + pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) { + match *self { + #[cfg(feature = "wayland")] + EventLoopWindowTarget::Wayland(_) => (), + #[cfg(feature = "x11")] + EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter), + } + } + + pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { + x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle()) + } } fn sticky_exit_callback( diff --git a/src/platform_impl/linux/wayland/env.rs b/src/platform_impl/linux/wayland/env.rs index ae213cb64a..f3e9b2401c 100644 --- a/src/platform_impl/linux/wayland/env.rs +++ b/src/platform_impl/linux/wayland/env.rs @@ -24,23 +24,23 @@ use sctk::shm::ShmHandler; /// Set of extra features that are supported by the compositor. #[derive(Debug, Clone, Copy)] pub struct WindowingFeatures { - cursor_grab: bool, + pointer_constraints: bool, xdg_activation: bool, } impl WindowingFeatures { /// Create `WindowingFeatures` based on the presented interfaces. pub fn new(env: &Environment) -> Self { - let cursor_grab = env.get_global::().is_some(); + let pointer_constraints = env.get_global::().is_some(); let xdg_activation = env.get_global::().is_some(); Self { - cursor_grab, + pointer_constraints, xdg_activation, } } - pub fn cursor_grab(&self) -> bool { - self.cursor_grab + pub fn pointer_constraints(&self) -> bool { + self.pointer_constraints } pub fn xdg_activation(&self) -> bool { diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 84dcb9fabb..01b47f47d9 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -6,6 +6,8 @@ use std::process; use std::rc::Rc; use std::time::{Duration, Instant}; +use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; + use sctk::reexports::client::protocol::wl_compositor::WlCompositor; use sctk::reexports::client::protocol::wl_shm::WlShm; use sctk::reexports::client::Display; @@ -24,7 +26,7 @@ use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget use super::env::{WindowingFeatures, WinitEnv}; use super::output::OutputManager; use super::seat::SeatManager; -use super::window::shim::{self, WindowRequest, WindowUpdate}; +use super::window::shim::{self, WindowUpdate}; use super::{DeviceId, WindowId}; mod proxy; @@ -64,13 +66,21 @@ pub struct EventLoopWindowTarget { /// Theme manager to manage cursors. /// - /// It's being shared amoung all windows to avoid loading + /// It's being shared between all windows to avoid loading /// multiple similar themes. pub theme_manager: ThemeManager, _marker: std::marker::PhantomData, } +impl EventLoopWindowTarget { + pub fn raw_display_handle(&self) -> RawDisplayHandle { + let mut display_handle = WaylandDisplayHandle::empty(); + display_handle.display = self.display.get_display_ptr() as *mut _; + RawDisplayHandle::Wayland(display_handle) + } +} + pub struct EventLoop { /// Event loop. event_loop: calloop::EventLoop<'static, WinitState>, @@ -222,6 +232,10 @@ impl EventLoop { &mut control_flow, ); + // NB: For consistency all platforms must emit a 'resumed' event even though Wayland + // applications don't themselves have a formal suspend/resume lifecycle. + callback(Event::Resumed, &self.window_target, &mut control_flow); + let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new(); let mut event_sink_back_buffer = Vec::new(); @@ -344,8 +358,7 @@ impl EventLoop { } // Process 'new' pending updates. - self.with_window_target(|window_target| { - let state = window_target.state.get_mut(); + self.with_state(|state| { window_updates.clear(); window_updates.extend( state @@ -357,8 +370,7 @@ impl EventLoop { for (window_id, window_update) in window_updates.iter_mut() { if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) { - let mut physical_size = self.with_window_target(|window_target| { - let state = window_target.state.get_mut(); + let mut physical_size = self.with_state(|state| { let window_handle = state.window_map.get(window_id).unwrap(); let mut size = window_handle.size.lock().unwrap(); @@ -371,9 +383,7 @@ impl EventLoop { sticky_exit_callback( Event::WindowEvent { - window_id: crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(*window_id), - ), + window_id: crate::window::WindowId(*window_id), event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size: &mut physical_size, @@ -391,8 +401,7 @@ impl EventLoop { } if let Some(size) = window_update.size.take() { - let physical_size = self.with_window_target(|window_target| { - let state = window_target.state.get_mut(); + let physical_size = self.with_state(|state| { let window_handle = state.window_map.get_mut(window_id).unwrap(); let mut window_size = window_handle.size.lock().unwrap(); @@ -418,24 +427,13 @@ impl EventLoop { // Mark that refresh isn't required, since we've done it right now. window_update.refresh_frame = false; - // Queue request for redraw into the next iteration of the loop, since - // resize and scale factor changes are double buffered. - window_handle - .pending_window_requests - .lock() - .unwrap() - .push(WindowRequest::Redraw); - window_target.event_loop_awakener.ping(); - physical_size }); if let Some(physical_size) = physical_size { sticky_exit_callback( Event::WindowEvent { - window_id: crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(*window_id), - ), + window_id: crate::window::WindowId(*window_id), event: WindowEvent::Resized(physical_size), }, &self.window_target, @@ -448,9 +446,7 @@ impl EventLoop { if window_update.close_window { sticky_exit_callback( Event::WindowEvent { - window_id: crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(*window_id), - ), + window_id: crate::window::WindowId(*window_id), event: WindowEvent::CloseRequested, }, &self.window_target, @@ -463,8 +459,7 @@ impl EventLoop { // The purpose of the back buffer and that swap is to not hold borrow_mut when // we're doing callback to the user, since we can double borrow if the user decides // to create a window in one of those callbacks. - self.with_window_target(|window_target| { - let state = window_target.state.get_mut(); + self.with_state(|state| { std::mem::swap( &mut event_sink_back_buffer, &mut state.event_sink.window_events, @@ -489,8 +484,7 @@ impl EventLoop { for (window_id, window_update) in window_updates.iter() { // Handle refresh of the frame. if window_update.refresh_frame { - self.with_window_target(|window_target| { - let state = window_target.state.get_mut(); + self.with_state(|state| { let window_handle = state.window_map.get_mut(window_id).unwrap(); window_handle.window.refresh(); if !window_update.redraw_requested { @@ -502,9 +496,7 @@ impl EventLoop { // Handle redraw request. if window_update.redraw_requested { sticky_exit_callback( - Event::RedrawRequested(crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(*window_id), - )), + Event::RedrawRequested(crate::window::WindowId(*window_id)), &self.window_target, &mut control_flow, &mut callback, @@ -535,9 +527,9 @@ impl EventLoop { &self.window_target } - fn with_window_target) -> U>(&mut self, f: F) -> U { + fn with_state U>(&mut self, f: F) -> U { let state = match &mut self.window_target.p { - PlatformEventLoopWindowTarget::Wayland(window_target) => window_target, + PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), #[cfg(feature = "x11")] _ => unreachable!(), }; @@ -552,6 +544,8 @@ impl EventLoop { _ => unreachable!(), }; - self.event_loop.dispatch(timeout, state) + self.event_loop + .dispatch(timeout, state) + .map_err(|error| error.into()) } } diff --git a/src/platform_impl/linux/wayland/event_loop/sink.rs b/src/platform_impl/linux/wayland/event_loop/sink.rs index 303ab826e4..26a895e569 100644 --- a/src/platform_impl/linux/wayland/event_loop/sink.rs +++ b/src/platform_impl/linux/wayland/event_loop/sink.rs @@ -1,7 +1,7 @@ //! An event loop's sink to deliver events from the Wayland event callbacks. use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent}; -use crate::platform_impl::platform::{DeviceId as PlatformDeviceId, WindowId as PlatformWindowId}; +use crate::platform_impl::platform::DeviceId as PlatformDeviceId; use crate::window::WindowId as RootWindowId; use super::{DeviceId, WindowId}; @@ -30,7 +30,7 @@ impl EventSink { pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) { self.window_events.push(Event::WindowEvent { event, - window_id: RootWindowId(PlatformWindowId::Wayland(window_id)), + window_id: RootWindowId(window_id), }); } } diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 4ed564aec0..9871b2f23e 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -8,6 +8,7 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface; +pub use crate::platform_impl::platform::WindowId; pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; pub use output::{MonitorHandle, VideoMode}; pub use window::Window; @@ -27,16 +28,7 @@ impl DeviceId { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(usize); - -impl WindowId { - pub const unsafe fn dummy() -> Self { - WindowId(0) - } -} - #[inline] fn make_wid(surface: &WlSurface) -> WindowId { - WindowId(surface.as_ref().c_ptr() as usize) + WindowId(surface.as_ref().c_ptr() as u64) } diff --git a/src/platform_impl/linux/wayland/output.rs b/src/platform_impl/linux/wayland/output.rs index 79cc523f04..e3a434781e 100644 --- a/src/platform_impl/linux/wayland/output.rs +++ b/src/platform_impl/linux/wayland/output.rs @@ -167,6 +167,16 @@ impl MonitorHandle { .into() } + #[inline] + pub fn refresh_rate_millihertz(&self) -> Option { + sctk::output::with_output_info(&self.proxy, |info| { + info.modes + .iter() + .find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32)) + }) + .flatten() + } + #[inline] pub fn scale_factor(&self) -> i32 { sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1) @@ -175,14 +185,14 @@ impl MonitorHandle { #[inline] pub fn video_modes(&self) -> impl Iterator { let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone()) - .unwrap_or_else(Vec::new); + .unwrap_or_default(); let monitor = self.clone(); modes.into_iter().map(move |mode| RootVideoMode { video_mode: PlatformVideoMode::Wayland(VideoMode { size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), - refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16, + refresh_rate_millihertz: mode.refresh_rate as u32, bit_depth: 32, monitor: monitor.clone(), }), @@ -194,7 +204,7 @@ impl MonitorHandle { pub struct VideoMode { pub(crate) size: PhysicalSize, pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, + pub(crate) refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, } @@ -210,8 +220,8 @@ impl VideoMode { } #[inline] - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate + pub fn refresh_rate_millihertz(&self) -> u32 { + self.refresh_rate_millihertz } pub fn monitor(&self) -> RootMonitorHandle { diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index c6e0ad456e..262a014bac 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -7,7 +7,7 @@ use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::Attached; -use sctk::reexports::calloop::{LoopHandle, RegistrationToken}; +use sctk::reexports::calloop::LoopHandle; use sctk::seat::keyboard; @@ -20,12 +20,6 @@ mod keymap; pub(crate) struct Keyboard { pub keyboard: WlKeyboard, - - /// The source for repeat keys. - pub repeat_token: Option, - - /// LoopHandle to drop `RepeatSource`, when dropping the keyboard. - pub loop_handle: LoopHandle<'static, WinitState>, } impl Keyboard { @@ -35,7 +29,7 @@ impl Keyboard { modifiers_state: Rc>, ) -> Option { let mut inner = KeyboardInner::new(modifiers_state); - let keyboard_data = keyboard::map_keyboard_repeat( + let keyboard = keyboard::map_keyboard_repeat( loop_handle.clone(), seat, None, @@ -44,15 +38,10 @@ impl Keyboard { let winit_state = dispatch_data.get::().unwrap(); handlers::handle_keyboard(event, &mut inner, winit_state); }, - ); - - let (keyboard, repeat_token) = keyboard_data.ok()?; + ) + .ok()?; - Some(Self { - keyboard, - loop_handle, - repeat_token: Some(repeat_token), - }) + Some(Self { keyboard }) } } @@ -61,10 +50,6 @@ impl Drop for Keyboard { if self.keyboard.as_ref().version() >= 3 { self.keyboard.release(); } - - if let Some(repeat_token) = self.repeat_token.take() { - self.loop_handle.remove(repeat_token); - } } } diff --git a/src/platform_impl/linux/wayland/seat/pointer/data.rs b/src/platform_impl/linux/wayland/seat/pointer/data.rs index 310e692b43..17e7a57a07 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/data.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/data.rs @@ -5,8 +5,9 @@ use std::rc::Rc; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Attached; -use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1}; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; use crate::event::{ModifiersState, TouchPhase}; @@ -25,6 +26,7 @@ pub(super) struct PointerData { pub pointer_constraints: Option>, pub confined_pointer: Rc>>, + pub locked_pointer: Rc>>, /// Latest observed serial in pointer events. pub latest_serial: Rc>, @@ -39,6 +41,7 @@ pub(super) struct PointerData { impl PointerData { pub fn new( confined_pointer: Rc>>, + locked_pointer: Rc>>, pointer_constraints: Option>, modifiers_state: Rc>, ) -> Self { @@ -47,6 +50,7 @@ impl PointerData { latest_serial: Rc::new(Cell::new(0)), latest_enter_serial: Rc::new(Cell::new(0)), confined_pointer, + locked_pointer, modifiers_state, pointer_constraints, axis_data: AxisData::new(), diff --git a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs index 1967861dd0..7348cc66f1 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs @@ -60,6 +60,7 @@ pub(super) fn handle_pointer( let winit_pointer = WinitPointer { pointer, confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), + locked_pointer: Rc::downgrade(&pointer_data.locked_pointer), pointer_constraints: pointer_data.pointer_constraints.clone(), latest_serial: pointer_data.latest_serial.clone(), latest_enter_serial: pointer_data.latest_enter_serial.clone(), @@ -104,6 +105,7 @@ pub(super) fn handle_pointer( let winit_pointer = WinitPointer { pointer, confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), + locked_pointer: Rc::downgrade(&pointer_data.locked_pointer), pointer_constraints: pointer_data.pointer_constraints.clone(), latest_serial: pointer_data.latest_serial.clone(), latest_enter_serial: pointer_data.latest_enter_serial.clone(), @@ -300,17 +302,17 @@ pub(super) fn handle_pointer( #[inline] pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) { - match event { - RelativePointerEvent::RelativeMotion { - dx_unaccel, - dy_unaccel, - .. - } => winit_state.event_sink.push_device_event( + if let RelativePointerEvent::RelativeMotion { + dx_unaccel, + dy_unaccel, + .. + } = event + { + winit_state.event_sink.push_device_event( DeviceEvent::MouseMotion { delta: (dx_unaccel, dy_unaccel), }, DeviceId, - ), - _ => (), + ) } } diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index 7ec116405b..b43deebb38 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -11,6 +11,7 @@ use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_rela use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime}; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; use sctk::seat::pointer::{ThemeManager, ThemedPointer}; use sctk::window::Window; @@ -35,9 +36,13 @@ pub struct WinitPointer { /// Cursor to handle confine requests. confined_pointer: Weak>>, + /// Cursor to handle locked requests. + locked_pointer: Weak>>, + /// Latest observed serial in pointer events. /// used by Window::start_interactive_move() latest_serial: Rc>, + /// Latest observed serial in pointer enter events. /// used by Window::set_cursor() latest_enter_serial: Rc>, @@ -157,6 +162,52 @@ impl WinitPointer { } } + pub fn lock(&self, surface: &WlSurface) { + let pointer_constraints = match &self.pointer_constraints { + Some(pointer_constraints) => pointer_constraints, + None => return, + }; + + let locked_pointer = match self.locked_pointer.upgrade() { + Some(locked_pointer) => locked_pointer, + // A pointer is gone. + None => return, + }; + + *locked_pointer.borrow_mut() = Some(init_locked_pointer( + pointer_constraints, + surface, + &*self.pointer, + )); + } + + pub fn unlock(&self) { + let locked_pointer = match self.locked_pointer.upgrade() { + Some(locked_pointer) => locked_pointer, + // A pointer is gone. + None => return, + }; + + let mut locked_pointer = locked_pointer.borrow_mut(); + + if let Some(locked_pointer) = locked_pointer.take() { + locked_pointer.destroy(); + } + } + + pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) { + let locked_pointer = match self.locked_pointer.upgrade() { + Some(locked_pointer) => locked_pointer, + // A pointer is gone. + None => return, + }; + + let locked_pointer = locked_pointer.borrow_mut(); + if let Some(locked_pointer) = locked_pointer.as_ref() { + locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into()); + } + } + pub fn drag_window(&self, window: &Window) { // WlPointer::setart_interactive_move() expects the last serial of *any* // pointer event (compare to set_cursor()). @@ -174,6 +225,9 @@ pub(super) struct Pointers { /// Confined pointer. confined_pointer: Rc>>, + + /// Locked pointer. + locked_pointer: Rc>>, } impl Pointers { @@ -185,11 +239,15 @@ impl Pointers { modifiers_state: Rc>, ) -> Self { let confined_pointer = Rc::new(RefCell::new(None)); + let locked_pointer = Rc::new(RefCell::new(None)); + let pointer_data = Rc::new(RefCell::new(PointerData::new( confined_pointer.clone(), + locked_pointer.clone(), pointer_constraints.clone(), modifiers_state, ))); + let pointer_seat = seat.detach(); let pointer = theme_manager.theme_pointer_with_impl( seat, @@ -216,6 +274,7 @@ impl Pointers { pointer, relative_pointer, confined_pointer, + locked_pointer, } } } @@ -232,6 +291,11 @@ impl Drop for Pointers { confined_pointer.destroy(); } + // Drop lock ponter. + if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() { + locked_pointer.destroy(); + } + // Drop the pointer itself in case it's possible. if self.pointer.as_ref().version() >= 3 { self.pointer.release(); @@ -243,7 +307,7 @@ pub(super) fn init_relative_pointer( relative_pointer_manager: &ZwpRelativePointerManagerV1, pointer: &WlPointer, ) -> ZwpRelativePointerV1 { - let relative_pointer = relative_pointer_manager.get_relative_pointer(&*pointer); + let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer); relative_pointer.quick_assign(move |_, event, mut dispatch_data| { let winit_state = dispatch_data.get::().unwrap(); handlers::handle_relative_pointer(event, winit_state); @@ -264,3 +328,16 @@ pub(super) fn init_confined_pointer( confined_pointer.detach() } + +pub(super) fn init_locked_pointer( + pointer_constraints: &Attached, + surface: &WlSurface, + pointer: &WlPointer, +) -> ZwpLockedPointerV1 { + let locked_pointer = + pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent); + + locked_pointer.quick_assign(move |_, _, _| {}); + + locked_pointer.detach() +} diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 6cb3daed75..a471cc138d 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -7,7 +7,9 @@ use sctk::reexports::client::Display; use sctk::reexports::calloop; -use raw_window_handle::WaylandHandle; +use raw_window_handle::{ + RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, +}; use sctk::window::Decorations; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; @@ -17,7 +19,9 @@ use crate::platform_impl::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; -use crate::window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes}; +use crate::window::{ + CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes, +}; use super::env::WindowingFeatures; use super::event_loop::WinitState; @@ -72,10 +76,13 @@ pub struct Window { /// Whether the window is decorated. decorated: AtomicBool, + + /// Grabbing mode. + cursor_grab_mode: Mutex, } impl Window { - pub fn new( + pub(crate) fn new( event_loop_window_target: &EventLoopWindowTarget, attributes: WindowAttributes, platform_attributes: PlatformAttributes, @@ -85,7 +92,7 @@ impl Window { .create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| { let winit_state = dispatch_data.get::().unwrap(); - // Get the window that receiced the event. + // Get the window that received the event. let window_id = super::make_wid(&surface); let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap(); @@ -249,6 +256,12 @@ impl Window { winit_state.window_map.insert(window_id, window_handle); + // On Wayland window doesn't have Focus by default and it'll get it later on. So be + // explicit here. + winit_state + .event_sink + .push_window_event(crate::event::WindowEvent::Focused(false), window_id); + winit_state .window_updates .insert(window_id, WindowUpdate::new()); @@ -285,6 +298,7 @@ impl Window { windowing_features, resizeable: AtomicBool::new(attributes.resizable), decorated: AtomicBool::new(attributes.decorations), + cursor_grab_mode: Mutex::new(CursorGrabMode::None), }; Ok(window) @@ -474,12 +488,17 @@ impl Window { } #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - if !self.windowing_features.cursor_grab() { + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + if !self.windowing_features.pointer_constraints() { + if mode == CursorGrabMode::None { + return Ok(()); + } + return Err(ExternalError::NotSupported(NotSupportedError::new())); } - self.send_request(WindowRequest::GrabCursor(grab)); + *self.cursor_grab_mode.lock().unwrap() = mode; + self.send_request(WindowRequest::SetCursorGrabMode(mode)); Ok(()) } @@ -494,15 +513,19 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> { - // XXX This is possible if the locked pointer is being used. We don't have any - // API for that right now, but it could be added in - // https://github.com/rust-windowing/winit/issues/1677. - // - // This function is essential for the locked pointer API. - // - // See pointer-constraints-unstable-v1.xml. - Err(ExternalError::NotSupported(NotSupportedError::new())) + pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { + // Positon can be set only for locked cursor. + if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked { + return Err(ExternalError::Os(os_error!(OsError::WaylandMisc( + "cursor position can be set only for locked cursor." + )))); + } + + let scale_factor = self.scale_factor() as f64; + let position = position.to_logical(scale_factor); + self.send_request(WindowRequest::SetLockedCursorPosition(position)); + + Ok(()) } #[inline] @@ -558,11 +581,17 @@ impl Window { } #[inline] - pub fn raw_window_handle(&self) -> WaylandHandle { - let mut handle = WaylandHandle::empty(); - handle.display = self.display.get_display_ptr() as *mut _; - handle.surface = self.surface.as_ref().c_ptr() as *mut _; - handle + pub fn raw_window_handle(&self) -> RawWindowHandle { + let mut window_handle = WaylandWindowHandle::empty(); + window_handle.surface = self.surface.as_ref().c_ptr() as *mut _; + RawWindowHandle::Wayland(window_handle) + } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + let mut display_handle = WaylandDisplayHandle::empty(); + display_handle.display = self.display.get_display_ptr() as *mut _; + RawDisplayHandle::Wayland(display_handle) } #[inline] diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs index 9789d5dbf5..13ff2dcdb0 100644 --- a/src/platform_impl/linux/wayland/window/shim.rs +++ b/src/platform_impl/linux/wayland/window/shim.rs @@ -1,4 +1,5 @@ use std::cell::Cell; +use std::mem::ManuallyDrop; use std::sync::{Arc, Mutex}; use sctk::reexports::client::protocol::wl_compositor::WlCompositor; @@ -19,7 +20,7 @@ use crate::platform_impl::wayland::event_loop::{EventSink, WinitState}; use crate::platform_impl::wayland::seat::pointer::WinitPointer; use crate::platform_impl::wayland::seat::text_input::TextInputHandler; use crate::platform_impl::wayland::WindowId; -use crate::window::{CursorIcon, Theme, UserAttentionType}; +use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType}; use super::WinitFrame; @@ -40,8 +41,11 @@ pub enum WindowRequest { /// Change the cursor icon. NewCursorIcon(CursorIcon), - /// Grab cursor. - GrabCursor(bool), + /// Change cursor grabbing mode. + SetCursorGrabMode(CursorGrabMode), + + /// Set cursor position. + SetLockedCursorPosition(LogicalPosition), /// Drag window. DragWindow, @@ -151,7 +155,7 @@ impl WindowUpdate { /// and react to events. pub struct WindowHandle { /// An actual window. - pub window: Window, + pub window: ManuallyDrop>, /// The current size of the window. pub size: Arc>>, @@ -172,7 +176,7 @@ pub struct WindowHandle { cursor_visible: Cell, /// Cursor confined to the surface. - confined: Cell, + cursor_grab_mode: Cell, /// Pointers over the current surface. pointers: Vec, @@ -203,12 +207,12 @@ impl WindowHandle { let compositor = env.get_global::().unwrap(); Self { - window, + window: ManuallyDrop::new(window), size, pending_window_requests, cursor_icon: Cell::new(CursorIcon::Default), is_resizable: Cell::new(true), - confined: Cell::new(false), + cursor_grab_mode: Cell::new(CursorGrabMode::None), cursor_visible: Cell::new(true), pointers: Vec::new(), text_inputs: Vec::new(), @@ -219,24 +223,37 @@ impl WindowHandle { } } - pub fn set_cursor_grab(&self, grab: bool) { + pub fn set_cursor_grab(&self, mode: CursorGrabMode) { // The new requested state matches the current confine status, return. - if self.confined.get() == grab { + let old_mode = self.cursor_grab_mode.replace(mode); + if old_mode == mode { return; } - self.confined.replace(grab); + // Clear old pointer data. + match old_mode { + CursorGrabMode::None => (), + CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()), + CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()), + } - for pointer in self.pointers.iter() { - if self.confined.get() { - let surface = self.window.surface(); - pointer.confine(surface); - } else { - pointer.unconfine(); + let surface = self.window.surface(); + match mode { + CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)), + CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)), + CursorGrabMode::None => { + // Current lock/confine was already removed. } } } + pub fn set_locked_cursor_position(&self, position: LogicalPosition) { + // XXX the cursor locking is ensured inside `Window`. + self.pointers + .iter() + .for_each(|p| p.set_cursor_position(position.x, position.y)); + } + pub fn set_user_attention(&self, request_type: Option) { let xdg_activation = match self.xdg_activation.as_ref() { None => return, @@ -284,10 +301,13 @@ impl WindowHandle { let position = self.pointers.iter().position(|p| *p == pointer); if position.is_none() { - if self.confined.get() { - let surface = self.window.surface(); - pointer.confine(surface); + let surface = self.window.surface(); + match self.cursor_grab_mode.get() { + CursorGrabMode::None => (), + CursorGrabMode::Locked => pointer.lock(surface), + CursorGrabMode::Confined => pointer.confine(surface), } + self.pointers.push(pointer); } @@ -302,9 +322,11 @@ impl WindowHandle { if let Some(position) = position { let pointer = self.pointers.remove(position); - // Drop the confined pointer. - if self.confined.get() { - pointer.unconfine(); + // Drop the grabbing mode. + match self.cursor_grab_mode.get() { + CursorGrabMode::None => (), + CursorGrabMode::Locked => pointer.unlock(), + CursorGrabMode::Confined => pointer.unconfine(), } } } @@ -406,7 +428,8 @@ pub fn handle_window_requests(winit_state: &mut WinitState) { // Process the rest of the events. for (window_id, window_handle) in window_map.iter_mut() { let mut requests = window_handle.pending_window_requests.lock().unwrap(); - for request in requests.drain(..) { + let requests = requests.drain(..); + for request in requests { match request { WindowRequest::Fullscreen(fullscreen) => { window_handle.window.set_fullscreen(fullscreen.as_ref()); @@ -427,8 +450,11 @@ pub fn handle_window_requests(winit_state: &mut WinitState) { let event_sink = &mut winit_state.event_sink; window_handle.set_ime_allowed(allow, event_sink); } - WindowRequest::GrabCursor(grab) => { - window_handle.set_cursor_grab(grab); + WindowRequest::SetCursorGrabMode(mode) => { + window_handle.set_cursor_grab(mode); + } + WindowRequest::SetLockedCursorPosition(position) => { + window_handle.set_locked_cursor_position(position); } WindowRequest::DragWindow => { window_handle.drag_window(); @@ -538,3 +564,14 @@ pub fn handle_window_requests(winit_state: &mut WinitState) { let _ = window_updates.remove(&window); } } + +impl Drop for WindowHandle { + fn drop(&mut self) { + unsafe { + let surface = self.window.surface().clone(); + // The window must be destroyed before wl_surface. + ManuallyDrop::drop(&mut self.window); + surface.destroy(); + } + } +} diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 0db198bbab..54cdc59b03 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -22,7 +22,7 @@ use crate::{ event_loop::EventLoopWindowTarget as RootELW, }; -/// The X11 documentation states: "Keycodes lie in the inclusive range [8,255]". +/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`". const KEYCODE_OFFSET: u8 = 8; pub(super) struct EventProcessor { @@ -49,7 +49,7 @@ impl EventProcessor { let mut devices = self.devices.borrow_mut(); if let Some(info) = DeviceInfo::get(&wt.xconn, device) { for info in info.iter() { - devices.insert(DeviceId(info.deviceid), Device::new(self, info)); + devices.insert(DeviceId(info.deviceid), Device::new(info)); } } } @@ -59,7 +59,7 @@ impl EventProcessor { F: Fn(&Arc) -> Ret, { let mut deleted = false; - let window_id = WindowId(window_id); + let window_id = WindowId(window_id as u64); let wt = get_xtarget(&self.target); let result = wt .windows @@ -414,7 +414,7 @@ impl EventProcessor { // resizing by dragging across monitors *without* dropping the window. let (width, height) = shared_state_lock .dpi_adjusted - .unwrap_or_else(|| (xev.width as u32, xev.height as u32)); + .unwrap_or((xev.width as u32, xev.height as u32)); let last_scale_factor = shared_state_lock.last_monitor.scale_factor; let new_scale_factor = { @@ -513,7 +513,7 @@ impl EventProcessor { // In the event that the window's been destroyed without being dropped first, we // cleanup again here. - wt.windows.borrow_mut().remove(&WindowId(window)); + wt.windows.borrow_mut().remove(&WindowId(window as u64)); // Since all XIM stuff needs to happen from the same thread, we destroy the input // context here instead of when dropping the window. @@ -531,8 +531,13 @@ impl EventProcessor { ffi::VisibilityNotify => { let xev: &ffi::XVisibilityEvent = xev.as_ref(); let xwindow = xev.window; - - self.with_window(xwindow, |window| window.visibility_notify()); + callback(Event::WindowEvent { + window_id: mkwid(xwindow), + event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured), + }); + self.with_window(xwindow, |window| { + window.visibility_notify(); + }); } ffi::Expose => { @@ -907,6 +912,8 @@ impl EventProcessor { if self.active_window != Some(xev.event) { self.active_window = Some(xev.event); + wt.update_device_event_filter(true); + let window_id = mkwid(xev.event); let position = PhysicalPosition::new(xev.event_x, xev.event_y); @@ -956,6 +963,7 @@ impl EventProcessor { if !self.window_exists(xev.event) { return; } + wt.ime .borrow_mut() .unfocus(xev.event) @@ -964,6 +972,8 @@ impl EventProcessor { if self.active_window.take() == Some(xev.event) { let window_id = mkwid(xev.event); + wt.update_device_event_filter(false); + // Issue key release events for all pressed keys Self::handle_pressed_keys( wt, @@ -1208,11 +1218,7 @@ impl EventProcessor { &*window.shared_state.lock(), ); - let window_id = crate::window::WindowId( - crate::platform_impl::platform::WindowId::X( - *window_id, - ), - ); + let window_id = crate::window::WindowId(*window_id); let old_inner_size = PhysicalSize::new(width, height); let mut new_inner_size = PhysicalSize::new(new_width, new_height); @@ -1253,46 +1259,48 @@ impl EventProcessor { } } - match self.ime_event_receiver.try_recv() { - Ok((window, event)) => match event { - ImeEvent::Enabled => { - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Enabled), - }); - } - ImeEvent::Start => { - self.is_composing = true; - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)), - }); - } - ImeEvent::Update(text, position) => { - if self.is_composing { - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))), - }); - } - } - ImeEvent::End => { - self.is_composing = false; - // Issue empty preedit on `Done`. - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), - }); - } - ImeEvent::Disabled => { - self.is_composing = false; + let (window, event) = match self.ime_event_receiver.try_recv() { + Ok((window, event)) => (window, event), + Err(_) => return, + }; + + match event { + ImeEvent::Enabled => { + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Enabled), + }); + } + ImeEvent::Start => { + self.is_composing = true; + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)), + }); + } + ImeEvent::Update(text, position) => { + if self.is_composing { callback(Event::WindowEvent { window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Disabled), + event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))), }); } - }, - Err(_) => (), + } + ImeEvent::End => { + self.is_composing = false; + // Issue empty preedit on `Done`. + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), + }); + } + ImeEvent::Disabled => { + self.is_composing = false; + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Disabled), + }); + } } } diff --git a/src/platform_impl/linux/x11/events.rs b/src/platform_impl/linux/x11/events.rs index 1c34b0d0cb..9063916025 100644 --- a/src/platform_impl/linux/x11/events.rs +++ b/src/platform_impl/linux/x11/events.rs @@ -161,7 +161,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { ffi::XK_Shift_R => VirtualKeyCode::RShift, ffi::XK_Control_L => VirtualKeyCode::LControl, ffi::XK_Control_R => VirtualKeyCode::RControl, - //ffi::XK_Caps_Lock => VirtualKeyCode::Caps_lock, + ffi::XK_Caps_Lock => VirtualKeyCode::Capital, //ffi::XK_Shift_Lock => VirtualKeyCode::Shift_lock, //ffi::XK_Meta_L => VirtualKeyCode::Meta_l, //ffi::XK_Meta_R => VirtualKeyCode::Meta_r, diff --git a/src/platform_impl/linux/x11/ime/context.rs b/src/platform_impl/linux/x11/ime/context.rs index 2fdfc6f320..a3109d57db 100644 --- a/src/platform_impl/linux/x11/ime/context.rs +++ b/src/platform_impl/linux/x11/ime/context.rs @@ -1,13 +1,14 @@ -use std::mem::transmute; +use std::ffi::CStr; use std::os::raw::c_short; -use std::ptr; use std::sync::Arc; +use std::{mem, ptr}; -use super::{ffi, util, XConnection, XError}; -use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; -use std::ffi::CStr; use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct}; +use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; + +use super::{ffi, util, XConnection, XError}; + /// IME creation error. #[derive(Debug)] pub enum ImeContextCreationError { @@ -65,12 +66,10 @@ extern "C" fn preedit_done_callback( .expect("failed to send preedit end event"); } -fn calc_byte_position(text: &Vec, pos: usize) -> usize { - let mut byte_pos = 0; - for i in 0..pos { - byte_pos += text[i].len_utf8(); - } - byte_pos +fn calc_byte_position(text: &[char], pos: usize) -> usize { + text.iter() + .take(pos) + .fold(0, |byte_pos, text| byte_pos + text.len_utf8()) } /// Preedit text information to be drawn inline by the client. @@ -103,7 +102,14 @@ extern "C" fn preedit_draw_callback( if xim_text.encoding_is_wchar > 0 { return; } - let new_text = unsafe { CStr::from_ptr(xim_text.string.multi_byte) }; + + let new_text = unsafe { xim_text.string.multi_byte }; + + if new_text.is_null() { + return; + } + + let new_text = unsafe { CStr::from_ptr(new_text) }; String::from(new_text.to_str().expect("Invalid UTF-8 String from IME")) .chars() @@ -158,7 +164,7 @@ struct PreeditCallbacks { impl PreeditCallbacks { pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks { let start_callback = create_xim_callback(client_data, unsafe { - transmute(preedit_start_callback as usize) + mem::transmute(preedit_start_callback as usize) }); let done_callback = create_xim_callback(client_data, preedit_done_callback); let caret_callback = create_xim_callback(client_data, preedit_caret_callback); diff --git a/src/platform_impl/linux/x11/ime/input_method.rs b/src/platform_impl/linux/x11/ime/input_method.rs index e552f55635..a747f972a9 100644 --- a/src/platform_impl/linux/x11/ime/input_method.rs +++ b/src/platform_impl/linux/x11/ime/input_method.rs @@ -7,13 +7,12 @@ use std::{ sync::Arc, }; +use once_cell::sync::Lazy; use parking_lot::Mutex; use super::{ffi, util, XConnection, XError}; -lazy_static! { - static ref GLOBAL_LOCK: Mutex<()> = Default::default(); -} +static GLOBAL_LOCK: Lazy> = Lazy::new(Default::default); unsafe fn open_im(xconn: &Arc, locale_modifiers: &CStr) -> Option { let _lock = GLOBAL_LOCK.lock(); @@ -181,10 +180,10 @@ impl PotentialInputMethod { } // By logging this struct, you get a sequential listing of every locale modifier tried, where it -// came from, and if it succceeded. +// came from, and if it succeeded. #[derive(Debug, Clone)] pub struct PotentialInputMethods { - // On correctly configured systems, the XMODIFIERS environemnt variable tells us everything we + // On correctly configured systems, the XMODIFIERS environment variable tells us everything we // need to know. xmodifiers: Option, // We have some standard options at our disposal that should ostensibly always work. For users @@ -214,7 +213,7 @@ impl PotentialInputMethods { // that case, we get `None` and end up skipping ahead to the next method. xmodifiers, fallbacks: [ - // This is a standard input method that supports compose equences, which should + // This is a standard input method that supports compose sequences, which should // always be available. `@im=none` appears to mean the same thing. PotentialInputMethod::from_str("@im=local"), // This explicitly specifies to use the implementation-dependent default, though diff --git a/src/platform_impl/linux/x11/ime/mod.rs b/src/platform_impl/linux/x11/ime/mod.rs index 4125c0ac0a..746ba9ea90 100644 --- a/src/platform_impl/linux/x11/ime/mod.rs +++ b/src/platform_impl/linux/x11/ime/mod.rs @@ -120,7 +120,33 @@ impl Ime { // Create empty entry in map, so that when IME is rebuilt, this window has a context. None } else { - let event = if with_preedit { + let context = unsafe { + ImeContext::new( + &self.inner.xconn, + self.inner.im, + window, + None, + with_preedit, + self.inner.event_sender.clone(), + ) + .or_else(|_| { + debug!( + "failed to create an IME context {} preedit support", + if with_preedit { "with" } else { "without" } + ); + ImeContext::new( + &self.inner.xconn, + self.inner.im, + window, + None, + !with_preedit, + self.inner.event_sender.clone(), + ) + }) + }?; + + // Check the state on the context, since it could fail to enable or disable preedit. + let event = if context.is_allowed { ImeEvent::Enabled } else { // There's no IME without preedit. @@ -132,16 +158,7 @@ impl Ime { .send((window, event)) .expect("Failed to send enabled event"); - Some(unsafe { - ImeContext::new( - &self.inner.xconn, - self.inner.im, - window, - None, - with_preedit, - self.inner.event_sender.clone(), - ) - }?) + Some(context) }; self.inner.contexts.insert(window, context); Ok(!self.is_destroyed()) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 491e37d6d9..c34eb2d460 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -23,7 +23,7 @@ pub use self::{ }; use std::{ - cell::RefCell, + cell::{Cell, RefCell}, collections::{HashMap, HashSet}, ffi::CStr, mem::{self, MaybeUninit}, @@ -40,6 +40,7 @@ use std::{ use libc::{self, setlocale, LC_CTYPE}; use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker}; +use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use self::{ dnd::{Dnd, DndState}, @@ -50,8 +51,13 @@ use self::{ use crate::{ error::OsError as RootOsError, event::{Event, StartCause}, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes}, + event_loop::{ + ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, + }, + platform_impl::{ + platform::{sticky_exit_callback, WindowId}, + PlatformSpecificWindowBuilderAttributes, + }, window::WindowAttributes, }; @@ -76,15 +82,16 @@ impl PeekableReceiver { if self.first.is_some() { return true; } + match self.recv.try_recv() { Ok(v) => { self.first = Some(v); - return true; + true } - Err(TryRecvError::Empty) => return false, + Err(TryRecvError::Empty) => false, Err(TryRecvError::Disconnected) => { warn!("Channel was disconnected when checking incoming"); - return false; + false } } } @@ -105,6 +112,7 @@ pub struct EventLoopWindowTarget { ime: RefCell, windows: RefCell>>, redraw_sender: WakeSender, + device_event_filter: Cell, _marker: ::std::marker::PhantomData, } @@ -229,21 +237,27 @@ impl EventLoop { let (user_sender, user_channel) = std::sync::mpsc::channel(); let (redraw_sender, redraw_channel) = std::sync::mpsc::channel(); + let window_target = EventLoopWindowTarget { + ime, + root, + windows: Default::default(), + _marker: ::std::marker::PhantomData, + ime_sender, + xconn, + wm_delete_window, + net_wm_ping, + redraw_sender: WakeSender { + sender: redraw_sender, // not used again so no clone + waker: waker.clone(), + }, + device_event_filter: Default::default(), + }; + + // Set initial device event filter. + window_target.update_device_event_filter(true); + let target = Rc::new(RootELW { - p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { - ime, - root, - windows: Default::default(), - _marker: ::std::marker::PhantomData, - ime_sender, - xconn, - wm_delete_window, - net_wm_ping, - redraw_sender: WakeSender { - sender: redraw_sender, // not used again so no clone - waker: waker.clone(), - }, - }), + p: super::EventLoopWindowTarget::X(window_target), _marker: ::std::marker::PhantomData, }); @@ -319,6 +333,17 @@ impl EventLoop { callback, ); + // NB: For consistency all platforms must emit a 'resumed' event even though X11 + // applications don't themselves have a formal suspend/resume lifecycle. + if *cause == StartCause::Init { + sticky_exit_callback( + crate::event::Event::Resumed, + &this.target, + control_flow, + callback, + ); + } + // Process all pending events this.drain_events(callback, control_flow); @@ -351,7 +376,7 @@ impl EventLoop { } for window_id in windows { - let window_id = crate::window::WindowId(super::WindowId::X(window_id)); + let window_id = crate::window::WindowId(window_id); sticky_exit_callback( Event::RedrawRequested(window_id), &this.target, @@ -407,11 +432,12 @@ impl EventLoop { deadline = Some(*wait_deadline); } } - return IterationResult { + + IterationResult { wait_start: start, deadline, timeout, - }; + } } let mut control_flow = ControlFlow::default(); @@ -443,7 +469,7 @@ impl EventLoop { // must do this because during the execution of the iteration we sometimes wake // the mio waker, and if the waker is already awaken before we call poll(), // then poll doesn't block, but it returns immediately. This caused the event - // loop to run continously even if the control_flow was `Wait` + // loop to run continuously even if the control_flow was `Wait` continue; } } @@ -494,10 +520,7 @@ impl EventLoop { target, control_flow, &mut |event, window_target, control_flow| { - if let Event::RedrawRequested(crate::window::WindowId( - super::WindowId::X(wid), - )) = event - { + if let Event::RedrawRequested(crate::window::WindowId(wid)) = event { wt.redraw_sender.sender.send(wid).unwrap(); wt.redraw_sender.waker.wake().unwrap(); } else { @@ -524,6 +547,37 @@ impl EventLoopWindowTarget { pub fn x_connection(&self) -> &Arc { &self.xconn } + + pub fn set_device_event_filter(&self, filter: DeviceEventFilter) { + self.device_event_filter.set(filter); + } + + /// Update the device event filter based on window focus. + pub fn update_device_event_filter(&self, focus: bool) { + let filter_events = self.device_event_filter.get() == DeviceEventFilter::Never + || (self.device_event_filter.get() == DeviceEventFilter::Unfocused && !focus); + + let mut mask = 0; + if !filter_events { + mask = ffi::XI_RawMotionMask + | ffi::XI_RawButtonPressMask + | ffi::XI_RawButtonReleaseMask + | ffi::XI_RawKeyPressMask + | ffi::XI_RawKeyReleaseMask; + } + + self.xconn + .select_xinput_events(self.root, ffi::XIAllMasterDevices, mask) + .queue(); + } + + pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { + let mut display_handle = XlibDisplayHandle::empty(); + display_handle.display = self.xconn.display as *mut _; + display_handle.screen = + unsafe { (self.xconn.xlib.XDefaultScreen)(self.xconn.display as *mut _) }; + RawDisplayHandle::Xlib(display_handle) + } } impl EventLoopProxy { @@ -575,15 +629,6 @@ impl<'a> Deref for DeviceInfo<'a> { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(ffi::Window); - -impl WindowId { - pub const unsafe fn dummy() -> Self { - WindowId(0) - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(c_int); @@ -604,7 +649,7 @@ impl Deref for Window { } impl Window { - pub fn new( + pub(crate) fn new( event_loop: &EventLoopWindowTarget, attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, @@ -623,7 +668,7 @@ impl Drop for Window { let window = self.deref(); let xconn = &window.xconn; unsafe { - (xconn.xlib.XDestroyWindow)(xconn.display, window.id().0); + (xconn.xlib.XDestroyWindow)(xconn.display, window.id().0 as ffi::Window); // If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about. let _ = xconn.check_errors(); } @@ -638,10 +683,7 @@ struct GenericEventCookie<'a> { } impl<'a> GenericEventCookie<'a> { - fn from_event<'b>( - xconn: &'b XConnection, - event: ffi::XEvent, - ) -> Option> { + fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option> { unsafe { let mut cookie: ffi::XGenericEventCookie = From::from(event); if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True { @@ -669,7 +711,7 @@ struct XExtension { } fn mkwid(w: ffi::Window) -> crate::window::WindowId { - crate::window::WindowId(crate::platform_impl::WindowId::X(WindowId(w))) + crate::window::WindowId(crate::platform_impl::platform::WindowId(w as u64)) } fn mkdid(w: c_int) -> crate::event::DeviceId { crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w))) @@ -698,24 +740,11 @@ enum ScrollOrientation { } impl Device { - fn new(el: &EventProcessor, info: &ffi::XIDeviceInfo) -> Self { + fn new(info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); - let wt = get_xtarget(&el.target); - if Device::physical_device(info) { - // Register for global raw events - let mask = ffi::XI_RawMotionMask - | ffi::XI_RawButtonPressMask - | ffi::XI_RawButtonReleaseMask - | ffi::XI_RawKeyPressMask - | ffi::XI_RawKeyReleaseMask; - // The request buffer is flushed when we poll for events - wt.xconn - .select_xinput_events(wt.root, info.deviceid, mask) - .queue(); - // Identify scroll axes for class_ptr in Device::classes(info) { let class = unsafe { &**class_ptr }; diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 2bd0ab17fb..8a696e8a05 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -1,11 +1,13 @@ use std::os::raw::*; +use std::slice; +use once_cell::sync::Lazy; use parking_lot::Mutex; use super::{ ffi::{ RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask, - RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources, + RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources, }, util, XConnection, XError, }; @@ -18,9 +20,7 @@ use crate::{ // Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; -lazy_static! { - static ref MONITORS: Mutex>> = Mutex::default(); -} +static MONITORS: Lazy>>> = Lazy::new(Mutex::default); pub fn invalidate_cached_monitor_list() -> Option> { // We update this lazily. @@ -31,7 +31,7 @@ pub fn invalidate_cached_monitor_list() -> Option> { pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, + pub(crate) refresh_rate_millihertz: u32, pub(crate) native_mode: RRMode, pub(crate) monitor: Option, } @@ -48,8 +48,8 @@ impl VideoMode { } #[inline] - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate + pub fn refresh_rate_millihertz(&self) -> u32 { + self.refresh_rate_millihertz } #[inline] @@ -72,6 +72,8 @@ pub struct MonitorHandle { position: (i32, i32), /// If the monitor is the primary one primary: bool, + /// The refresh rate used by monitor. + refresh_rate_millihertz: Option, /// The DPI scale factor pub(crate) scale_factor: f64, /// Used to determine which windows are on this monitor @@ -106,6 +108,15 @@ impl std::hash::Hash for MonitorHandle { } } +#[inline] +pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option { + if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 { + Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32) + } else { + None + } +} + impl MonitorHandle { fn new( xconn: &XConnection, @@ -117,10 +128,22 @@ impl MonitorHandle { let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) }; let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) }; + + // Get the refresh rate of the current video mode. + let current_mode = unsafe { (*crtc).mode }; + let screen_modes = + unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) }; + let refresh_rate_millihertz = screen_modes + .iter() + .find(|mode| mode.id == current_mode) + .and_then(mode_refresh_rate_millihertz); + let rect = util::AaRect::new(position, dimensions); + Some(MonitorHandle { id, name, + refresh_rate_millihertz, scale_factor, dimensions, position, @@ -137,6 +160,7 @@ impl MonitorHandle { scale_factor: 1.0, dimensions: (1, 1), position: (0, 0), + refresh_rate_millihertz: None, primary: true, rect: util::AaRect::new((0, 0), (1, 1)), video_modes: Vec::new(), @@ -165,6 +189,10 @@ impl MonitorHandle { self.position.into() } + pub fn refresh_rate_millihertz(&self) -> Option { + self.refresh_rate_millihertz + } + #[inline] pub fn scale_factor(&self) -> f64 { self.scale_factor @@ -230,11 +258,11 @@ impl XConnection { panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist."); } - let mut available; let mut has_primary = false; let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root); - available = Vec::with_capacity((*resources).ncrtc as usize); + let mut 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); diff --git a/src/platform_impl/linux/x11/util/atom.rs b/src/platform_impl/linux/x11/util/atom.rs index 4138722483..5bfa386a49 100644 --- a/src/platform_impl/linux/x11/util/atom.rs +++ b/src/platform_impl/linux/x11/util/atom.rs @@ -5,15 +5,14 @@ use std::{ os::raw::*, }; +use once_cell::sync::Lazy; use parking_lot::Mutex; use super::*; type AtomCache = HashMap; -lazy_static! { - static ref ATOM_CACHE: Mutex = Mutex::new(HashMap::with_capacity(2048)); -} +static ATOM_CACHE: Lazy> = Lazy::new(|| Mutex::new(HashMap::with_capacity(2048))); impl XConnection { pub fn get_atom + Debug>(&self, name: T) -> ffi::Atom { diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index 17aeca5584..8a0c8a5b96 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -98,7 +98,7 @@ pub struct LogicalFrameExtents { pub bottom: f64, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum FrameExtentsHeuristicPath { Supported, UnsupportedNested, @@ -370,8 +370,12 @@ impl XConnection { let top = offset_y; let bottom = diff_y.saturating_sub(offset_y); - let frame_extents = - FrameExtents::new(left.into(), right.into(), top.into(), bottom.into()); + let frame_extents = FrameExtents::new( + left as c_ulong, + right as c_ulong, + top as c_ulong, + bottom as c_ulong, + ); FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedNested, @@ -379,7 +383,7 @@ impl XConnection { } else { // This is the case for xmonad and dwm, AKA the only WMs tested that supplied a // border value. This is convenient, since we can use it to get an accurate frame. - let frame_extents = FrameExtents::from_border(border.into()); + let frame_extents = FrameExtents::from_border(border as c_ulong); FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedBordered, diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index 222d81748e..a2dbe6a170 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -173,6 +173,12 @@ impl MotifHints { } } +impl Default for MotifHints { + fn default() -> Self { + Self::new() + } +} + impl MwmHints { fn as_slice(&self) -> &[c_ulong] { unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) } @@ -317,7 +323,7 @@ impl XConnection { let mut hints = MotifHints::new(); if let Ok(props) = self.get_property::(window, motif_hints, motif_hints) { - hints.hints.flags = props.get(0).cloned().unwrap_or(0); + hints.hints.flags = props.first().cloned().unwrap_or(0); hints.hints.functions = props.get(1).cloned().unwrap_or(0); hints.hints.decorations = props.get(2).cloned().unwrap_or(0); hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long; diff --git a/src/platform_impl/linux/x11/util/icon.rs b/src/platform_impl/linux/x11/util/icon.rs index b0253e1b96..3240a9f896 100644 --- a/src/platform_impl/linux/x11/util/icon.rs +++ b/src/platform_impl/linux/x11/util/icon.rs @@ -1,3 +1,5 @@ +#![allow(clippy::assertions_on_constants)] + use super::*; use crate::icon::{Icon, Pixel, PIXEL_SIZE}; diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index cb380c7d41..2ce46e70e5 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -4,6 +4,7 @@ use super::{ ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, *, }; +use crate::platform_impl::platform::x11::monitor; use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; /// Represents values of `WINIT_HIDPI_FACTOR`. @@ -80,18 +81,13 @@ impl XConnection { // XRROutputInfo contains an array of mode ids that correspond to // modes in the array in XRRScreenResources .filter(|x| output_modes.iter().any(|id| x.id == *id)) - .map(|x| { - let refresh_rate = if x.dotClock > 0 && x.hTotal > 0 && x.vTotal > 0 { - x.dotClock as u64 * 1000 / (x.hTotal as u64 * x.vTotal as u64) - } else { - 0 - }; - + .map(|mode| { VideoMode { - size: (x.width, x.height), - refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16, + size: (mode.width, mode.height), + refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode) + .unwrap_or(0), bit_depth: bit_depth as u16, - native_mode: x.id, + native_mode: mode.id, // This is populated in `MonitorHandle::video_modes` as the // video mode is returned to the user monitor: None, @@ -164,7 +160,9 @@ impl XConnection { (self.xrandr.XRRFreeOutputInfo)(output_info); Some((name, scale_factor, modes)) } - pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> { + + #[must_use] + pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> { unsafe { let mut major = 0; let mut minor = 0; @@ -195,12 +193,13 @@ impl XConnection { (self.xrandr.XRRFreeScreenResources)(resources); if status == Success as i32 { - Ok(()) + Some(()) } else { - Err(()) + None } } } + pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode { unsafe { let mut major = 0; diff --git a/src/platform_impl/linux/x11/util/window_property.rs b/src/platform_impl/linux/x11/util/window_property.rs index 8bf718ea8e..e93d5bb072 100644 --- a/src/platform_impl/linux/x11/util/window_property.rs +++ b/src/platform_impl/linux/x11/util/window_property.rs @@ -58,7 +58,7 @@ impl XConnection { property, // This offset is in terms of 32-bit chunks. offset, - // This is the quanity of 32-bit chunks to receive at once. + // This is the quantity of 32-bit chunks to receive at once. PROPERTY_BUFFER_SIZE, ffi::False, property_type, diff --git a/src/platform_impl/linux/x11/util/wm.rs b/src/platform_impl/linux/x11/util/wm.rs index 6fef5a3c4d..89e9a0b8ad 100644 --- a/src/platform_impl/linux/x11/util/wm.rs +++ b/src/platform_impl/linux/x11/util/wm.rs @@ -1,12 +1,12 @@ +use once_cell::sync::Lazy; use parking_lot::Mutex; use super::*; // This info is global to the window manager. -lazy_static! { - static ref SUPPORTED_HINTS: Mutex> = Mutex::new(Vec::with_capacity(0)); - static ref WM_NAME: Mutex> = Mutex::new(None); -} +static SUPPORTED_HINTS: Lazy>> = + Lazy::new(|| Mutex::new(Vec::with_capacity(0))); +static WM_NAME: Lazy>> = Lazy::new(|| Mutex::new(None)); pub fn hint_is_supported(hint: ffi::Atom) -> bool { (*SUPPORTED_HINTS.lock()).contains(&hint) @@ -60,7 +60,7 @@ impl XConnection { let root_window_wm_check = { let result = self.get_property(root, check_atom, ffi::XA_WINDOW); - let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned()); + let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); wm_check? }; @@ -70,7 +70,7 @@ impl XConnection { let child_window_wm_check = { let result = self.get_property(root_window_wm_check, check_atom, ffi::XA_WINDOW); - let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned()); + let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); wm_check? }; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index fb2047cc5a..ab7cddfa42 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,4 +1,3 @@ -use raw_window_handle::XlibHandle; use std::{ cmp, env, ffi::CString, @@ -8,10 +7,11 @@ use std::{ ptr, slice, sync::Arc, }; -use x11_dl::xlib::TrueColor; use libc; use parking_lot::Mutex; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle}; +use x11_dl::xlib::TrueColor; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, @@ -22,7 +22,7 @@ use crate::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, - window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes}, + window::{CursorGrabMode, CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes}, }; use super::{ @@ -106,7 +106,7 @@ pub struct UnownedWindow { root: ffi::Window, // never changes screen_id: i32, // never changes cursor: Mutex, - cursor_grabbed: Mutex, + cursor_grabbed_mode: Mutex, cursor_visible: Mutex, ime_sender: Mutex, pub shared_state: Mutex, @@ -114,7 +114,7 @@ pub struct UnownedWindow { } impl UnownedWindow { - pub fn new( + pub(crate) fn new( event_loop: &EventLoopWindowTarget, window_attrs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, @@ -276,7 +276,7 @@ impl UnownedWindow { root, screen_id, cursor: Default::default(), - cursor_grabbed: Mutex::new(false), + cursor_grabbed_mode: Mutex::new(CursorGrabMode::None), cursor_visible: Mutex::new(true), ime_sender: Mutex::new(event_loop.ime_sender.clone()), shared_state: SharedState::new(guessed_monitor, &window_attrs), @@ -371,15 +371,15 @@ impl UnownedWindow { } else { max_inner_size = Some(dimensions.into()); min_inner_size = Some(dimensions.into()); - - let mut shared_state = window.shared_state.get_mut(); - shared_state.min_inner_size = window_attrs.min_inner_size; - shared_state.max_inner_size = window_attrs.max_inner_size; - shared_state.resize_increments = pl_attribs.resize_increments; - shared_state.base_size = pl_attribs.base_size; } } + let mut shared_state = window.shared_state.get_mut(); + shared_state.min_inner_size = min_inner_size.map(Into::into); + shared_state.max_inner_size = max_inner_size.map(Into::into); + shared_state.resize_increments = pl_attribs.resize_increments; + shared_state.base_size = pl_attribs.base_size; + let mut normal_hints = util::NormalHints::new(xconn); normal_hints.set_position(position.map(|PhysicalPosition { x, y }| (x, y))); normal_hints.set_size(Some(dimensions)); @@ -1272,64 +1272,75 @@ impl UnownedWindow { } #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - let mut grabbed_lock = self.cursor_grabbed.lock(); - if grab == *grabbed_lock { + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + let mut grabbed_lock = self.cursor_grabbed_mode.lock(); + if mode == *grabbed_lock { return Ok(()); } + unsafe { // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`. // Therefore, this is common to both codepaths. (self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); } - let result = if grab { - let result = unsafe { - (self.xconn.xlib.XGrabPointer)( - self.xconn.display, - self.xwindow, - ffi::True, - (ffi::ButtonPressMask - | ffi::ButtonReleaseMask - | ffi::EnterWindowMask - | ffi::LeaveWindowMask - | ffi::PointerMotionMask - | ffi::PointerMotionHintMask - | ffi::Button1MotionMask - | ffi::Button2MotionMask - | ffi::Button3MotionMask - | ffi::Button4MotionMask - | ffi::Button5MotionMask - | ffi::ButtonMotionMask - | ffi::KeymapStateMask) as c_uint, - ffi::GrabModeAsync, - ffi::GrabModeAsync, - self.xwindow, - 0, - ffi::CurrentTime, - ) - }; - match result { - ffi::GrabSuccess => Ok(()), - ffi::AlreadyGrabbed => { - Err("Cursor could not be grabbed: already grabbed by another client") - } - ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"), - ffi::GrabNotViewable => { - Err("Cursor could not be grabbed: grab location not viewable") + let result = match mode { + CursorGrabMode::None => self + .xconn + .flush_requests() + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))), + CursorGrabMode::Confined => { + let result = unsafe { + (self.xconn.xlib.XGrabPointer)( + self.xconn.display, + self.xwindow, + ffi::True, + (ffi::ButtonPressMask + | ffi::ButtonReleaseMask + | ffi::EnterWindowMask + | ffi::LeaveWindowMask + | ffi::PointerMotionMask + | ffi::PointerMotionHintMask + | ffi::Button1MotionMask + | ffi::Button2MotionMask + | ffi::Button3MotionMask + | ffi::Button4MotionMask + | ffi::Button5MotionMask + | ffi::ButtonMotionMask + | ffi::KeymapStateMask) as c_uint, + ffi::GrabModeAsync, + ffi::GrabModeAsync, + self.xwindow, + 0, + ffi::CurrentTime, + ) + }; + + match result { + ffi::GrabSuccess => Ok(()), + ffi::AlreadyGrabbed => { + Err("Cursor could not be confined: already confined by another client") + } + ffi::GrabInvalidTime => Err("Cursor could not be confined: invalid time"), + ffi::GrabNotViewable => { + Err("Cursor could not be confined: confine location not viewable") + } + ffi::GrabFrozen => { + Err("Cursor could not be confined: frozen by another client") + } + _ => unreachable!(), } - ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"), - _ => unreachable!(), + .map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err)))) + } + CursorGrabMode::Locked => { + return Err(ExternalError::NotSupported(NotSupportedError::new())); } - .map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err)))) - } else { - self.xconn - .flush_requests() - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))) }; + if result.is_ok() { - *grabbed_lock = grab; + *grabbed_lock = mode; } + result } @@ -1386,14 +1397,14 @@ impl UnownedWindow { // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer` // if the cursor isn't currently grabbed - let mut grabbed_lock = self.cursor_grabbed.lock(); + let mut grabbed_lock = self.cursor_grabbed_mode.lock(); unsafe { (self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); } self.xconn .flush_requests() .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?; - *grabbed_lock = false; + *grabbed_lock = CursorGrabMode::None; // we keep the lock until we are done self.xconn @@ -1485,23 +1496,30 @@ impl UnownedWindow { #[inline] pub fn id(&self) -> WindowId { - WindowId(self.xwindow) + WindowId(self.xwindow as u64) } #[inline] pub fn request_redraw(&self) { self.redraw_sender .sender - .send(WindowId(self.xwindow)) + .send(WindowId(self.xwindow as u64)) .unwrap(); self.redraw_sender.waker.wake().unwrap(); } #[inline] - pub fn raw_window_handle(&self) -> XlibHandle { - let mut handle = XlibHandle::empty(); - handle.window = self.xlib_window(); - handle.display = self.xlib_display(); - handle + pub fn raw_window_handle(&self) -> RawWindowHandle { + let mut window_handle = XlibWindowHandle::empty(); + window_handle.window = self.xlib_window(); + RawWindowHandle::Xlib(window_handle) + } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + let mut display_handle = XlibDisplayHandle::empty(); + display_handle.display = self.xlib_display(); + display_handle.screen = self.screen_id; + RawDisplayHandle::Xlib(display_handle) } } diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index b5a5582cc3..a4a6ef0b4d 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -8,6 +8,7 @@ use objc::{ declare::ClassDecl, runtime::{Class, Object, Sel}, }; +use once_cell::sync::Lazy; use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; @@ -16,19 +17,17 @@ pub struct AppClass(pub *const Class); unsafe impl Send for AppClass {} unsafe impl Sync for AppClass {} -lazy_static! { - pub static ref APP_CLASS: AppClass = unsafe { - let superclass = class!(NSApplication); - let mut decl = ClassDecl::new("WinitApp", superclass).unwrap(); +pub static APP_CLASS: Lazy = Lazy::new(|| unsafe { + let superclass = class!(NSApplication); + let mut decl = ClassDecl::new("WinitApp", superclass).unwrap(); - decl.add_method( - sel!(sendEvent:), - send_event as extern "C" fn(&Object, Sel, id), - ); + decl.add_method( + sel!(sendEvent:), + send_event as extern "C" fn(&Object, Sel, id), + ); - AppClass(decl.register()) - }; -} + AppClass(decl.register()) +}); // Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index 940a5538e6..fc5ae7e407 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -1,14 +1,16 @@ -use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState}; +use std::{ + cell::{RefCell, RefMut}, + os::raw::c_void, +}; use cocoa::base::id; use objc::{ declare::ClassDecl, runtime::{Class, Object, Sel}, }; -use std::{ - cell::{RefCell, RefMut}, - os::raw::c_void, -}; +use once_cell::sync::Lazy; + +use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState}; static AUX_DELEGATE_STATE_NAME: &str = "auxState"; @@ -21,23 +23,26 @@ pub struct AppDelegateClass(pub *const Class); unsafe impl Send for AppDelegateClass {} unsafe impl Sync for AppDelegateClass {} -lazy_static! { - pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe { - let superclass = class!(NSResponder); - let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap(); +pub static APP_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap(); - decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id); - decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); + decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id); + decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); - decl.add_method( - sel!(applicationDidFinishLaunching:), - did_finish_launching as extern "C" fn(&Object, Sel, id), - ); - decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME); + decl.add_method( + sel!(applicationDidFinishLaunching:), + did_finish_launching as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillTerminate:), + will_terminate as extern "C" fn(&Object, Sel, id), + ); - AppDelegateClass(decl.register()) - }; -} + decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME); + + AppDelegateClass(decl.register()) +}); /// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> { @@ -67,7 +72,7 @@ extern "C" fn dealloc(this: &Object, _: Sel) { let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME)); // As soon as the box is constructed it is immediately dropped, releasing the underlying // memory - Box::from_raw(state_ptr as *mut RefCell); + drop(Box::from_raw(state_ptr as *mut RefCell)); } } @@ -75,3 +80,10 @@ extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) { trace_scope!("applicationDidFinishLaunching:"); AppState::launched(this); } + +extern "C" fn will_terminate(_this: &Object, _: Sel, _: id) { + trace!("Triggered `applicationWillTerminate`"); + // TODO: Notify every window that it will be destroyed, like done in iOS? + AppState::exit(); + trace!("Completed `applicationWillTerminate`"); +} diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 7b392a4124..711c99b158 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -21,6 +21,7 @@ use objc::{ rc::autoreleasepool, runtime::{Object, BOOL, NO, YES}, }; +use once_cell::sync::Lazy; use crate::{ dpi::LogicalSize, @@ -41,9 +42,7 @@ use crate::{ window::WindowId, }; -lazy_static! { - static ref HANDLER: Handler = Default::default(); -} +static HANDLER: Lazy = Lazy::new(Default::default); impl<'a, Never> Event<'a, Never> { fn userify(self) -> Event<'a, T> { @@ -127,7 +126,6 @@ impl EventHandler for EventLoopHandler { struct Handler { ready: AtomicBool, in_callback: AtomicBool, - dialog_is_closing: AtomicBool, control_flow: Mutex, control_flow_prev: Mutex, start_time: Mutex>, @@ -262,8 +260,6 @@ impl Handler { } } -pub static INTERRUPT_EVENT_LOOP_EXIT: AtomicBool = AtomicBool::new(false); - pub enum AppState {} impl AppState { @@ -306,6 +302,9 @@ impl AppState { HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( StartCause::Init, ))); + // NB: For consistency all platforms must emit a 'resumed' event even though macOS + // applications don't themselves have a formal suspend/resume lifecycle. + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)); HANDLER.set_in_callback(false); } @@ -403,40 +402,12 @@ impl AppState { if HANDLER.should_exit() { unsafe { let app: id = NSApp(); - let windows: id = msg_send![app, windows]; - let window_count: usize = msg_send![windows, count]; - - let dialog_open = if window_count > 1 { - let dialog: id = msg_send![windows, lastObject]; - let is_main_window: BOOL = msg_send![dialog, isMainWindow]; - let is_visible: BOOL = msg_send![dialog, isVisible]; - is_visible != NO && is_main_window == NO - } else { - false - }; - let dialog_is_closing = HANDLER.dialog_is_closing.load(Ordering::SeqCst); autoreleasepool(|| { - if !INTERRUPT_EVENT_LOOP_EXIT.load(Ordering::SeqCst) - && !dialog_open - && !dialog_is_closing - { - let () = msg_send![app, stop: nil]; - // To stop event loop immediately, we need to post some event here. - post_dummy_event(app); - } + let _: () = msg_send![app, stop: nil]; + // To stop event loop immediately, we need to post some event here. + post_dummy_event(app); }); - - if window_count > 0 { - let window: id = msg_send![windows, firstObject]; - let window_has_focus: BOOL = msg_send![window, isKeyWindow]; - if !dialog_open && window_has_focus != NO && dialog_is_closing { - HANDLER.dialog_is_closing.store(false, Ordering::SeqCst); - } - if dialog_open { - HANDLER.dialog_is_closing.store(true, Ordering::SeqCst); - } - } }; } HANDLER.update_start_time(); diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index cb069f8354..ea0315139c 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -17,6 +17,7 @@ use cocoa::{ foundation::{NSInteger, NSPoint, NSTimeInterval}, }; use objc::rc::autoreleasepool; +use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; use crate::{ event::Event, @@ -87,6 +88,11 @@ impl EventLoopWindowTarget { let monitor = monitor::primary_monitor(); Some(RootMonitorHandle { inner: monitor }) } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()) + } } impl EventLoopWindowTarget { @@ -120,7 +126,7 @@ pub struct EventLoop { _callback: Option>>, } -#[derive(Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) activation_policy: ActivationPolicy, pub(crate) default_menu: bool, @@ -210,10 +216,10 @@ impl EventLoop { // A bit of juggling with the callback references to make sure // that `self.callback` is the only owner of the callback. let weak_cb: Weak<_> = Rc::downgrade(&callback); - mem::drop(callback); + drop(callback); AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); - let () = msg_send![app, run]; + let _: () = msg_send![app, run]; if let Some(panic) = self.panic_info.take() { drop(self._callback.take()); @@ -246,7 +252,7 @@ pub unsafe fn post_dummy_event(target: id) { data1: 0 as NSInteger data2: 0 as NSInteger ]; - let () = msg_send![target, postEvent: dummy_event atStart: YES]; + let _: () = msg_send![target, postEvent: dummy_event atStart: YES]; } /// Catches panics that happen inside `f` and when a panic @@ -270,7 +276,7 @@ pub fn stop_app_on_panic R + UnwindSafe, R>( unsafe { let app_class = class!(NSApplication); let app: id = msg_send![app_class, sharedApplication]; - let () = msg_send![app, stop: nil]; + let _: () = msg_send![app, stop: nil]; // Posting a dummy event to get `stop` to take effect immediately. // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752 diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 229bd6598d..0a23e60d41 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -221,3 +221,45 @@ extern "C" { pub fn CGDisplayModeRetain(mode: CGDisplayModeRef); pub fn CGDisplayModeRelease(mode: CGDisplayModeRef); } + +mod core_video { + use super::*; + + #[link(name = "CoreVideo", kind = "framework")] + extern "C" {} + + // CVBase.h + + pub type CVTimeFlags = i32; // int32_t + pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0; + + #[repr(C)] + #[derive(Debug, Clone)] + pub struct CVTime { + pub time_value: i64, // int64_t + pub time_scale: i32, // int32_t + pub flags: i32, // int32_t + } + + // CVReturn.h + + pub type CVReturn = i32; // int32_t + pub const kCVReturnSuccess: CVReturn = 0; + + // CVDisplayLink.h + + pub type CVDisplayLinkRef = *mut c_void; + + extern "C" { + pub fn CVDisplayLinkCreateWithCGDisplay( + displayID: CGDirectDisplayID, + displayLinkOut: *mut CVDisplayLinkRef, + ) -> CVReturn; + pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod( + displayLink: CVDisplayLinkRef, + ) -> CVTime; + pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef); + } +} + +pub use core_video::*; diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 50e438ae44..4608e2fa42 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,4 +1,5 @@ #![cfg(target_os = "macos")] +#![allow(clippy::let_unit_value)] #[macro_use] mod util; @@ -69,7 +70,7 @@ impl Deref for Window { } impl Window { - pub fn new( + pub(crate) fn new( _window_target: &EventLoopWindowTarget, attributes: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 08dba6c9f9..2b93be6a5a 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -16,16 +16,12 @@ use core_foundation::{ string::CFString, }; use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; -use core_video_sys::{ - kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay, - CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease, -}; #[derive(Clone)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, + pub(crate) refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, pub(crate) native_mode: NativeDisplayMode, } @@ -34,7 +30,7 @@ impl PartialEq for VideoMode { fn eq(&self, other: &Self) -> bool { self.size == other.size && self.bit_depth == other.bit_depth - && self.refresh_rate == other.refresh_rate + && self.refresh_rate_millihertz == other.refresh_rate_millihertz && self.monitor == other.monitor } } @@ -45,7 +41,7 @@ impl std::hash::Hash for VideoMode { fn hash(&self, state: &mut H) { self.size.hash(state); self.bit_depth.hash(state); - self.refresh_rate.hash(state); + self.refresh_rate_millihertz.hash(state); self.monitor.hash(state); } } @@ -55,7 +51,7 @@ impl std::fmt::Debug for VideoMode { f.debug_struct("VideoMode") .field("size", &self.size) .field("bit_depth", &self.bit_depth) - .field("refresh_rate", &self.refresh_rate) + .field("refresh_rate_millihertz", &self.refresh_rate_millihertz) .field("monitor", &self.monitor) .finish() } @@ -91,8 +87,8 @@ impl VideoMode { self.bit_depth } - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate + pub fn refresh_rate_millihertz(&self) -> u32 { + self.refresh_rate_millihertz } pub fn monitor(&self) -> RootMonitorHandle { @@ -224,22 +220,25 @@ impl MonitorHandle { unsafe { NSScreen::backingScaleFactor(screen) as f64 } } - pub fn video_modes(&self) -> impl Iterator { - let cv_refresh_rate = unsafe { + pub fn refresh_rate_millihertz(&self) -> Option { + unsafe { let mut display_link = std::ptr::null_mut(); assert_eq!( - CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link), - kCVReturnSuccess + ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link), + ffi::kCVReturnSuccess ); - let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); - CVDisplayLinkRelease(display_link); + let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); + ffi::CVDisplayLinkRelease(display_link); // This value is indefinite if an invalid display link was specified - assert!(time.flags & kCVTimeIsIndefinite == 0); + assert!(time.flags & ffi::kCVTimeIsIndefinite == 0); - time.timeScale as i64 / time.timeValue - }; + Some((time.time_scale as i64 / time.time_value * 1000) as u32) + } + } + pub fn video_modes(&self) -> impl Iterator { + let refresh_rate_millihertz = self.refresh_rate_millihertz().unwrap_or(0); let monitor = self.clone(); unsafe { @@ -259,14 +258,15 @@ impl MonitorHandle { }; modes.into_iter().map(move |mode| { - let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; + let cg_refresh_rate_millihertz = + ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; // CGDisplayModeGetRefreshRate returns 0.0 for any display that // isn't a CRT - let refresh_rate = if cg_refresh_rate > 0 { - cg_refresh_rate + let refresh_rate_millihertz = if cg_refresh_rate_millihertz > 0 { + (cg_refresh_rate_millihertz * 1000) as u32 } else { - cv_refresh_rate + refresh_rate_millihertz }; let pixel_encoding = @@ -287,7 +287,7 @@ impl MonitorHandle { ffi::CGDisplayModeGetPixelWidth(mode) as u32, ffi::CGDisplayModeGetPixelHeight(mode) as u32, ), - refresh_rate: refresh_rate as u16, + refresh_rate_millihertz, bit_depth, monitor: monitor.clone(), native_mode: NativeDisplayMode(mode), diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index 308637cb81..2ced84ef5f 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -99,7 +99,8 @@ pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: pub enum CFRunLoopTimerContext {} /// This mirrors the struct with the same name from Core Foundation. -/// https://developer.apple.com/documentation/corefoundation/cfrunloopobservercontext?language=objc +/// +/// #[allow(non_snake_case)] #[repr(C)] pub struct CFRunLoopObserverContext { diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index afd62c2e9b..bf7e33ea72 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -167,11 +167,15 @@ pub unsafe fn set_maximized_async( shared_state_lock.maximized = maximized; - let curr_mask = ns_window.styleMask(); if shared_state_lock.fullscreen.is_some() { // Handle it in window_did_exit_fullscreen return; - } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { + } + + if ns_window + .styleMask() + .contains(NSWindowStyleMask::NSResizableWindowMask) + { // Just use the native zoom if resizable ns_window.zoom_(nil); } else { diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 75ff703a3d..859bdb9c9d 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -7,7 +7,7 @@ use std::ops::{BitAnd, Deref}; use std::os::raw::c_uchar; use cocoa::{ - appkit::{NSApp, NSWindowStyleMask}, + appkit::{CGFloat, NSApp, NSWindowStyleMask}, base::{id, nil}, foundation::{NSPoint, NSRect, NSString, NSUInteger}, }; @@ -33,7 +33,7 @@ pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { length: 0, }; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub struct IdRef(id); impl IdRef { @@ -61,7 +61,7 @@ impl Drop for IdRef { fn drop(&mut self) { if self.0 != nil { unsafe { - let () = msg_send![self.0, release]; + let _: () = msg_send![self.0, release]; }; } } @@ -113,7 +113,7 @@ impl Drop for TraceGuard { // 1. translate the bottom-left window corner into the top-left window corner // 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { - CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) + CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64 } /// Converts from winit screen-coordinates to macOS screen-coordinates. @@ -121,8 +121,8 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { /// macOS: bottom-left is (0, 0) and y increasing upwards pub fn window_position(position: LogicalPosition) -> NSPoint { NSPoint::new( - position.x, - CGDisplay::main().pixels_high() as f64 - position.y, + position.x as CGFloat, + CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat, ) } @@ -150,7 +150,7 @@ pub unsafe fn superclass(this: &Object) -> &Class { #[allow(dead_code)] pub unsafe fn open_emoji_picker() { - let () = msg_send![NSApp(), orderFrontCharacterPalette: nil]; + let _: () = msg_send![NSApp(), orderFrontCharacterPalette: nil]; } pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) { diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 1d7fdc86f9..1b40d76d9c 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -18,6 +18,7 @@ use objc::{ declare::ClassDecl, runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES}, }; +use once_cell::sync::Lazy; use crate::{ dpi::{LogicalPosition, LogicalSize}, @@ -53,11 +54,19 @@ impl Default for CursorState { } } -#[derive(Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] enum ImeState { + /// The IME events are disabled, so only `ReceivedCharacter` is being sent to the user. Disabled, + + /// The IME events are enabled. Enabled, + + /// The IME is in preedit. Preedit, + + /// The text was just commited, so the next input from the keyboard must be ignored. + Commited, } pub(super) struct ViewState { @@ -83,11 +92,9 @@ impl ViewState { fn get_scale_factor(&self) -> f64 { (unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64 } + fn is_ime_enabled(&self) -> bool { - match self.ime_state { - ImeState::Disabled => false, - _ => true, - } + !matches!(self.ime_state, ImeState::Disabled) } } @@ -155,180 +162,178 @@ struct ViewClass(*const Class); unsafe impl Send for ViewClass {} unsafe impl Sync for ViewClass {} -lazy_static! { - static ref VIEW_CLASS: ViewClass = unsafe { - let superclass = class!(NSView); - let mut decl = ClassDecl::new("WinitView", superclass).unwrap(); - decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); - decl.add_method( - sel!(initWithWinit:), - init_with_winit as extern "C" fn(&Object, Sel, *mut c_void) -> id, - ); - decl.add_method( - sel!(viewDidMoveToWindow), - view_did_move_to_window as extern "C" fn(&Object, Sel), - ); - decl.add_method( - sel!(drawRect:), - draw_rect as extern "C" fn(&Object, Sel, NSRect), - ); - decl.add_method( - sel!(acceptsFirstResponder), - accepts_first_responder as extern "C" fn(&Object, Sel) -> BOOL, - ); - decl.add_method( - sel!(touchBar), - touch_bar as extern "C" fn(&Object, Sel) -> BOOL, - ); - decl.add_method( - sel!(resetCursorRects), - reset_cursor_rects as extern "C" fn(&Object, Sel), - ); - - // ------------------------------------------------------------------ - // NSTextInputClient - decl.add_method( - sel!(hasMarkedText), - has_marked_text as extern "C" fn(&Object, Sel) -> BOOL, - ); - decl.add_method( - sel!(markedRange), - marked_range as extern "C" fn(&Object, Sel) -> NSRange, - ); - decl.add_method( - sel!(selectedRange), - selected_range as extern "C" fn(&Object, Sel) -> NSRange, - ); - decl.add_method( - sel!(setMarkedText:selectedRange:replacementRange:), - set_marked_text as extern "C" fn(&mut Object, Sel, id, NSRange, NSRange), - ); - decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel)); - decl.add_method( - sel!(validAttributesForMarkedText), - valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id, - ); - decl.add_method( - sel!(attributedSubstringForProposedRange:actualRange:), - attributed_substring_for_proposed_range - as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id, - ); - decl.add_method( - sel!(insertText:replacementRange:), - insert_text as extern "C" fn(&Object, Sel, id, NSRange), - ); - decl.add_method( - sel!(characterIndexForPoint:), - character_index_for_point as extern "C" fn(&Object, Sel, NSPoint) -> NSUInteger, - ); - decl.add_method( - sel!(firstRectForCharacterRange:actualRange:), - first_rect_for_character_range - as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> NSRect, - ); - decl.add_method( - sel!(doCommandBySelector:), - do_command_by_selector as extern "C" fn(&Object, Sel, Sel), - ); - // ------------------------------------------------------------------ - - decl.add_method(sel!(keyDown:), key_down as extern "C" fn(&Object, Sel, id)); - decl.add_method(sel!(keyUp:), key_up as extern "C" fn(&Object, Sel, id)); - decl.add_method( - sel!(flagsChanged:), - flags_changed as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(insertTab:), - insert_tab as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(insertBackTab:), - insert_back_tab as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(mouseDown:), - mouse_down as extern "C" fn(&Object, Sel, id), - ); - decl.add_method(sel!(mouseUp:), mouse_up as extern "C" fn(&Object, Sel, id)); - decl.add_method( - sel!(rightMouseDown:), - right_mouse_down as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(rightMouseUp:), - right_mouse_up as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(otherMouseDown:), - other_mouse_down as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(otherMouseUp:), - other_mouse_up as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(mouseMoved:), - mouse_moved as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(mouseDragged:), - mouse_dragged as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(rightMouseDragged:), - right_mouse_dragged as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(otherMouseDragged:), - other_mouse_dragged as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(mouseEntered:), - mouse_entered as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(mouseExited:), - mouse_exited as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(scrollWheel:), - scroll_wheel as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(pressureChangeWithEvent:), - pressure_change_with_event as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(_wantsKeyDownForEvent:), - wants_key_down_for_event as extern "C" fn(&Object, Sel, id) -> BOOL, - ); - decl.add_method( - sel!(cancelOperation:), - cancel_operation as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(frameDidChange:), - frame_did_change as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(acceptsFirstMouse:), - accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, - ); - decl.add_ivar::<*mut c_void>("winitState"); - decl.add_ivar::("markedText"); - let protocol = Protocol::get("NSTextInputClient").unwrap(); - decl.add_protocol(protocol); - ViewClass(decl.register()) - }; -} +static VIEW_CLASS: Lazy = Lazy::new(|| unsafe { + let superclass = class!(NSView); + let mut decl = ClassDecl::new("WinitView", superclass).unwrap(); + decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); + decl.add_method( + sel!(initWithWinit:), + init_with_winit as extern "C" fn(&Object, Sel, *mut c_void) -> id, + ); + decl.add_method( + sel!(viewDidMoveToWindow), + view_did_move_to_window as extern "C" fn(&Object, Sel), + ); + decl.add_method( + sel!(drawRect:), + draw_rect as extern "C" fn(&Object, Sel, NSRect), + ); + decl.add_method( + sel!(acceptsFirstResponder), + accepts_first_responder as extern "C" fn(&Object, Sel) -> BOOL, + ); + decl.add_method( + sel!(touchBar), + touch_bar as extern "C" fn(&Object, Sel) -> BOOL, + ); + decl.add_method( + sel!(resetCursorRects), + reset_cursor_rects as extern "C" fn(&Object, Sel), + ); + + // ------------------------------------------------------------------ + // NSTextInputClient + decl.add_method( + sel!(hasMarkedText), + has_marked_text as extern "C" fn(&Object, Sel) -> BOOL, + ); + decl.add_method( + sel!(markedRange), + marked_range as extern "C" fn(&Object, Sel) -> NSRange, + ); + decl.add_method( + sel!(selectedRange), + selected_range as extern "C" fn(&Object, Sel) -> NSRange, + ); + decl.add_method( + sel!(setMarkedText:selectedRange:replacementRange:), + set_marked_text as extern "C" fn(&mut Object, Sel, id, NSRange, NSRange), + ); + decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel)); + decl.add_method( + sel!(validAttributesForMarkedText), + valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id, + ); + decl.add_method( + sel!(attributedSubstringForProposedRange:actualRange:), + attributed_substring_for_proposed_range + as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id, + ); + decl.add_method( + sel!(insertText:replacementRange:), + insert_text as extern "C" fn(&Object, Sel, id, NSRange), + ); + decl.add_method( + sel!(characterIndexForPoint:), + character_index_for_point as extern "C" fn(&Object, Sel, NSPoint) -> NSUInteger, + ); + decl.add_method( + sel!(firstRectForCharacterRange:actualRange:), + first_rect_for_character_range + as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> NSRect, + ); + decl.add_method( + sel!(doCommandBySelector:), + do_command_by_selector as extern "C" fn(&Object, Sel, Sel), + ); + // ------------------------------------------------------------------ + + decl.add_method(sel!(keyDown:), key_down as extern "C" fn(&Object, Sel, id)); + decl.add_method(sel!(keyUp:), key_up as extern "C" fn(&Object, Sel, id)); + decl.add_method( + sel!(flagsChanged:), + flags_changed as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(insertTab:), + insert_tab as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(insertBackTab:), + insert_back_tab as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseDown:), + mouse_down as extern "C" fn(&Object, Sel, id), + ); + decl.add_method(sel!(mouseUp:), mouse_up as extern "C" fn(&Object, Sel, id)); + decl.add_method( + sel!(rightMouseDown:), + right_mouse_down as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(rightMouseUp:), + right_mouse_up as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseDown:), + other_mouse_down as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseUp:), + other_mouse_up as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseMoved:), + mouse_moved as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseDragged:), + mouse_dragged as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(rightMouseDragged:), + right_mouse_dragged as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseDragged:), + other_mouse_dragged as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseEntered:), + mouse_entered as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseExited:), + mouse_exited as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(scrollWheel:), + scroll_wheel as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(pressureChangeWithEvent:), + pressure_change_with_event as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(_wantsKeyDownForEvent:), + wants_key_down_for_event as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(cancelOperation:), + cancel_operation as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(frameDidChange:), + frame_did_change as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(acceptsFirstMouse:), + accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.add_ivar::<*mut c_void>("winitState"); + decl.add_ivar::("markedText"); + let protocol = Protocol::get("NSTextInputClient").unwrap(); + decl.add_protocol(protocol); + ViewClass(decl.register()) +}); extern "C" fn dealloc(this: &Object, _sel: Sel) { unsafe { let marked_text: id = *this.get_ivar("markedText"); let _: () = msg_send![marked_text, release]; let state: *mut c_void = *this.get_ivar("winitState"); - Box::from_raw(state as *mut ViewState); + drop(Box::from_raw(state as *mut ViewState)); } } @@ -423,7 +428,7 @@ extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { AppState::handle_redraw(WindowId(get_window_id(state.ns_window))); let superclass = util::superclass(this); - let () = msg_send![super(this, superclass), drawRect: rect]; + let _: () = msg_send![super(this, superclass), drawRect: rect]; } } @@ -530,17 +535,24 @@ extern "C" fn set_marked_text( })); } - let cursor_start = preedit_string.len(); - let cursor_end = preedit_string.len(); - state.ime_state = ImeState::Preedit; + // Don't update state to preedit when we've just commited a string, since the following + // preedit string will be None anyway. + if state.ime_state != ImeState::Commited { + state.ime_state = ImeState::Preedit; + } + + // Empty string basically means that there's no preedit, so indicate that by sending + // `None` cursor range. + let cursor_range = if preedit_string.is_empty() { + None + } else { + Some((preedit_string.len(), preedit_string.len())) + }; // Send WindowEvent for updating marked text AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Preedit( - preedit_string, - Some((cursor_start, cursor_end)), - )), + event: WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)), })); } } @@ -560,7 +572,7 @@ extern "C" fn unmark_text(this: &Object, _sel: Sel) { let state = &mut *(state_ptr as *mut ViewState); AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Preedit(String::new(), Some((0, 0)))), + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), })); if state.is_ime_enabled() { // Leave the Preedit state @@ -633,27 +645,32 @@ extern "C" fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_ran window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::Ime(Ime::Commit(string)), })); - state.ime_state = ImeState::Enabled; + state.ime_state = ImeState::Commited; } } } extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, _command: Sel) { trace_scope!("doCommandBySelector:"); - // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character - // happens, i.e. newlines, tabs, and Ctrl+C. + // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human + // readable" character happens, i.e. newlines, tabs, and Ctrl+C. unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); + // We shouldn't forward any character from just commited text, since we'll end up sending + // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, + // which is not desired given it was used to confirm IME input. + if state.ime_state == ImeState::Commited { + return; + } + state.forward_key_to_app = true; let has_marked_text: BOOL = msg_send![this, hasMarkedText]; - if has_marked_text == NO { - if state.ime_state == ImeState::Preedit { - // Leave preedit so that we also report the keyup for this key - state.ime_state = ImeState::Enabled; - } + if has_marked_text == NO && state.ime_state == ImeState::Preedit { + // Leave preedit so that we also report the keyup for this key + state.ime_state = ImeState::Enabled; } } } @@ -753,6 +770,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { // we must send the `KeyboardInput` event during IME if it triggered // `doCommandBySelector`. (doCommandBySelector means that the keyboard input // is not handled by IME and should be handled by the application) + let mut text_commited = false; if state.ime_allowed { let events_for_nsview: id = msg_send![class!(NSArray), arrayWithObject: event]; let _: () = msg_send![this, interpretKeyEvents: events_for_nsview]; @@ -763,6 +781,12 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { // some of the reads (eg `state.ime_state`) that happen after this // point are not needed. compiler_fence(Ordering::SeqCst); + + // If the text was commited we must treat the next keyboard event as IME related. + if state.ime_state == ImeState::Commited { + state.ime_state = ImeState::Enabled; + text_commited = true; + } } let now_in_preedit = state.ime_state == ImeState::Preedit; @@ -772,8 +796,9 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { update_potentially_stale_modifiers(state, event); - let preedit_related = was_in_preedit || now_in_preedit; - if !preedit_related || state.forward_key_to_app || !state.ime_allowed { + let ime_related = was_in_preedit || now_in_preedit || text_commited; + + if !ime_related || state.forward_key_to_app || !state.ime_allowed { #[allow(deprecated)] let window_event = Event::WindowEvent { window_id, @@ -901,7 +926,7 @@ extern "C" fn insert_tab(this: &Object, _sel: Sel, _sender: id) { let first_responder: id = msg_send![window, firstResponder]; let this_ptr = this as *const _ as *mut _; if first_responder == this_ptr { - let (): _ = msg_send![window, selectNextKeyView: this]; + let _: () = msg_send![window, selectNextKeyView: this]; } } } @@ -913,7 +938,7 @@ extern "C" fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) { let first_responder: id = msg_send![window, firstResponder]; let this_ptr = this as *const _ as *mut _; if first_responder == this_ptr { - let (): _ = msg_send![window, selectPreviousKeyView: this]; + let _: () = msg_send![window, selectPreviousKeyView: this]; } } } @@ -1188,7 +1213,7 @@ extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { event: WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure, - stage, + stage: stage as i64, }, }; diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index d4856cb91b..f804a275f2 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1,4 +1,3 @@ -use raw_window_handle::{AppKitHandle, RawWindowHandle}; use std::{ collections::VecDeque, convert::TryInto, @@ -10,6 +9,10 @@ use std::{ }, }; +use raw_window_handle::{ + AppKitDisplayHandle, AppKitWindowHandle, RawDisplayHandle, RawWindowHandle, +}; + use crate::{ dpi::{ LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, @@ -20,7 +23,6 @@ use crate::{ platform::macos::WindowExtMacOS, platform_impl::platform::{ app_state::AppState, - app_state::INTERRUPT_EVENT_LOOP_EXIT, ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, @@ -30,7 +32,8 @@ use crate::{ OsError, }, window::{ - CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, + WindowId as RootWindowId, }, }; use cocoa::{ @@ -47,6 +50,7 @@ use objc::{ rc::autoreleasepool, runtime::{Class, Object, Sel, BOOL, NO, YES}, }; +use once_cell::sync::Lazy; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub usize); @@ -57,6 +61,18 @@ impl WindowId { } } +impl From for u64 { + fn from(window_id: WindowId) -> Self { + window_id.0 as u64 + } +} + +impl From for WindowId { + fn from(raw_id: u64) -> Self { + Self(raw_id as usize) + } +} + // Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier // for the window. pub fn get_window_id(window_cocoa_id: id) -> WindowId { @@ -99,8 +115,13 @@ unsafe fn create_view( ) -> Option<(IdRef, Weak>)> { let (ns_view, cursor_state) = new_view(ns_window); ns_view.non_nil().map(|ns_view| { + // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until + // macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid + // always the default system value in favour of the user's code if !pl_attribs.disallow_hidpi { ns_view.setWantsBestResolutionOpenGLSurface_(YES); + } else { + ns_view.setWantsBestResolutionOpenGLSurface_(NO); } // On Mojave, views automatically become layer-backed shortly after being added to @@ -247,32 +268,30 @@ struct WindowClass(*const Class); unsafe impl Send for WindowClass {} unsafe impl Sync for WindowClass {} -lazy_static! { - static ref WINDOW_CLASS: WindowClass = unsafe { - let window_superclass = class!(NSWindow); - let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap(); +static WINDOW_CLASS: Lazy = Lazy::new(|| unsafe { + let window_superclass = class!(NSWindow); + let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap(); - pub extern "C" fn can_become_main_window(_: &Object, _: Sel) -> BOOL { - trace_scope!("canBecomeMainWindow"); - YES - } + pub extern "C" fn can_become_main_window(_: &Object, _: Sel) -> BOOL { + trace_scope!("canBecomeMainWindow"); + YES + } - pub extern "C" fn can_become_key_window(_: &Object, _: Sel) -> BOOL { - trace_scope!("canBecomeKeyWindow"); - YES - } + pub extern "C" fn can_become_key_window(_: &Object, _: Sel) -> BOOL { + trace_scope!("canBecomeKeyWindow"); + YES + } - decl.add_method( - sel!(canBecomeMainWindow), - can_become_main_window as extern "C" fn(&Object, Sel) -> BOOL, - ); - decl.add_method( - sel!(canBecomeKeyWindow), - can_become_key_window as extern "C" fn(&Object, Sel) -> BOOL, - ); - WindowClass(decl.register()) - }; -} + decl.add_method( + sel!(canBecomeMainWindow), + can_become_main_window as extern "C" fn(&Object, Sel) -> BOOL, + ); + decl.add_method( + sel!(canBecomeKeyWindow), + can_become_key_window as extern "C" fn(&Object, Sel) -> BOOL, + ); + WindowClass(decl.register()) +}); #[derive(Default)] pub struct SharedState { @@ -373,7 +392,7 @@ unsafe impl Send for UnownedWindow {} unsafe impl Sync for UnownedWindow {} impl UnownedWindow { - pub fn new( + pub(crate) fn new( mut win_attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result<(Arc, IdRef), RootOsError> { @@ -416,7 +435,7 @@ impl UnownedWindow { use cocoa::foundation::NSArray; // register for drag and drop operations. - let () = msg_send![ + let _: () = msg_send![ *ns_window, registerForDraggedTypes: NSArray::arrayWithObject(nil, appkit::NSFilenamesPboardType) @@ -623,9 +642,17 @@ impl UnownedWindow { } #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + let associate_mouse_cursor = match mode { + CursorGrabMode::Locked => false, + CursorGrabMode::None => true, + CursorGrabMode::Confined => { + return Err(ExternalError::NotSupported(NotSupportedError::new())) + } + }; + // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 - CGDisplay::associate_mouse_and_mouse_cursor_position(!grab) + CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor) .map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status)))) } @@ -907,8 +934,6 @@ impl UnownedWindow { let mut shared_state_lock = self.lock_shared_state("set_fullscreen"); shared_state_lock.fullscreen = fullscreen.clone(); - INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); - match (&old_fullscreen, &fullscreen) { (&None, &Some(_)) => unsafe { util::toggle_full_screen_async( @@ -955,7 +980,7 @@ impl UnownedWindow { | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; app.setPresentationOptions_(presentation_options); - let () = msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1]; + let _: () = msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1]; }, ( &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), @@ -973,12 +998,12 @@ impl UnownedWindow { // Restore the normal window level following the Borderless fullscreen // `CGShieldingWindowLevel() + 1` hack. - let () = msg_send![ + let _: () = msg_send![ *self.ns_window, setLevel: ffi::NSWindowLevel::NSNormalWindowLevel ]; }, - _ => INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst), + _ => {} }; } @@ -1122,10 +1147,15 @@ impl UnownedWindow { #[inline] pub fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = AppKitHandle::empty(); - handle.ns_window = *self.ns_window as *mut _; - handle.ns_view = *self.ns_view as *mut _; - RawWindowHandle::AppKit(handle) + let mut window_handle = AppKitWindowHandle::empty(); + window_handle.ns_window = *self.ns_window as *mut _; + window_handle.ns_view = *self.ns_view as *mut _; + RawWindowHandle::AppKit(window_handle) + } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()) } } @@ -1259,10 +1289,11 @@ unsafe fn set_min_inner_size(window: V, mut min_size: Logica // Convert from client area size to window size min_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 min_size.height += (current_rect.size.height - content_rect.size.height) as f64; - window.setMinSize_(NSSize { + let min_size = NSSize { width: min_size.width as CGFloat, height: min_size.height as CGFloat, - }); + }; + window.setMinSize_(min_size); // If necessary, resize the window to match constraint if current_rect.size.width < min_size.width { current_rect.size.width = min_size.width; @@ -1283,10 +1314,11 @@ unsafe fn set_max_inner_size(window: V, mut max_size: Logica // Convert from client area size to window size max_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 max_size.height += (current_rect.size.height - content_rect.size.height) as f64; - window.setMaxSize_(NSSize { + let max_size = NSSize { width: max_size.width as CGFloat, height: max_size.height as CGFloat, - }); + }; + window.setMaxSize_(max_size); // If necessary, resize the window to match constraint if current_rect.size.width > max_size.width { current_rect.size.width = max_size.width; diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 01a8c5fa8c..001c89fdd3 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,11 +1,11 @@ use std::{ f64, os::raw::c_void, - sync::{atomic::Ordering, Arc, Weak}, + sync::{Arc, Weak}, }; use cocoa::{ - appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow}, + appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow, NSWindowOcclusionState}, base::{id, nil}, foundation::NSUInteger, }; @@ -14,13 +14,13 @@ use objc::{ rc::autoreleasepool, runtime::{Class, Object, Sel, BOOL, NO, YES}, }; +use once_cell::sync::Lazy; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{Event, ModifiersState, WindowEvent}, platform_impl::platform::{ app_state::AppState, - app_state::INTERRUPT_EVENT_LOOP_EXIT, event::{EventProxy, EventWrapper}, util::{self, IdRef}, view::ViewState, @@ -135,97 +135,99 @@ struct WindowDelegateClass(*const Class); unsafe impl Send for WindowDelegateClass {} unsafe impl Sync for WindowDelegateClass {} -lazy_static! { - static ref WINDOW_DELEGATE_CLASS: WindowDelegateClass = unsafe { - let superclass = class!(NSResponder); - let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap(); - - decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); - decl.add_method( - sel!(initWithWinit:), - init_with_winit as extern "C" fn(&Object, Sel, *mut c_void) -> id, - ); - - decl.add_method( - sel!(windowShouldClose:), - window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, - ); - decl.add_method( - sel!(windowWillClose:), - window_will_close as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidResize:), - window_did_resize as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidMove:), - window_did_move as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidChangeBackingProperties:), - window_did_change_backing_properties as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidBecomeKey:), - window_did_become_key as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidResignKey:), - window_did_resign_key as extern "C" fn(&Object, Sel, id), - ); - - decl.add_method( - sel!(draggingEntered:), - dragging_entered as extern "C" fn(&Object, Sel, id) -> BOOL, - ); - decl.add_method( - sel!(prepareForDragOperation:), - prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, - ); - decl.add_method( - sel!(performDragOperation:), - perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, - ); - decl.add_method( - sel!(concludeDragOperation:), - conclude_drag_operation as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(draggingExited:), - dragging_exited as extern "C" fn(&Object, Sel, id), - ); - - decl.add_method( - sel!(window:willUseFullScreenPresentationOptions:), - window_will_use_fullscreen_presentation_options - as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger, - ); - decl.add_method( - sel!(windowDidEnterFullScreen:), - window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowWillEnterFullScreen:), - window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidExitFullScreen:), - window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowWillExitFullScreen:), - window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidFailToEnterFullScreen:), - window_did_fail_to_enter_fullscreen as extern "C" fn(&Object, Sel, id), - ); - - decl.add_ivar::<*mut c_void>("winitState"); - WindowDelegateClass(decl.register()) - }; -} +static WINDOW_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap(); + + decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); + decl.add_method( + sel!(initWithWinit:), + init_with_winit as extern "C" fn(&Object, Sel, *mut c_void) -> id, + ); + + decl.add_method( + sel!(windowShouldClose:), + window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(windowWillClose:), + window_will_close as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidResize:), + window_did_resize as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidMove:), + window_did_move as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidChangeBackingProperties:), + window_did_change_backing_properties as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidBecomeKey:), + window_did_become_key as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidResignKey:), + window_did_resign_key as extern "C" fn(&Object, Sel, id), + ); + + decl.add_method( + sel!(draggingEntered:), + dragging_entered as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(prepareForDragOperation:), + prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(performDragOperation:), + perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(concludeDragOperation:), + conclude_drag_operation as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(draggingExited:), + dragging_exited as extern "C" fn(&Object, Sel, id), + ); + + decl.add_method( + sel!(window:willUseFullScreenPresentationOptions:), + window_will_use_fullscreen_presentation_options + as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger, + ); + decl.add_method( + sel!(windowDidEnterFullScreen:), + window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowWillEnterFullScreen:), + window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidExitFullScreen:), + window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowWillExitFullScreen:), + window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidFailToEnterFullScreen:), + window_did_fail_to_enter_fullscreen as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidChangeOcclusionState:), + window_did_change_occlusion_state as extern "C" fn(&Object, Sel, id), + ); + + decl.add_ivar::<*mut c_void>("winitState"); + WindowDelegateClass(decl.register()) +}); // This function is definitely unsafe, but labeling that would increase // boilerplate and wouldn't really clarify anything... @@ -239,7 +241,7 @@ fn with_state T, T>(this: &Object, callba extern "C" fn dealloc(this: &Object, _sel: Sel) { with_state(this, |state| unsafe { - Box::from_raw(state as *mut WindowDelegateState); + drop(Box::from_raw(state as *mut WindowDelegateState)); }); } @@ -249,7 +251,7 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i if this != nil { (*this).set_ivar("winitState", state); with_state(&*this, |state| { - let () = msg_send![*state.ns_window, setDelegate: this]; + let _: () = msg_send![*state.ns_window, setDelegate: this]; }); } this @@ -269,7 +271,7 @@ extern "C" fn window_will_close(this: &Object, _: Sel, _: id) { autoreleasepool(|| { // Since El Capitan, we need to be careful that delegate methods can't // be called after the window closes. - let () = msg_send![*state.ns_window, setDelegate: nil]; + let _: () = msg_send![*state.ns_window, setDelegate: nil]; }); state.emit_event(WindowEvent::Destroyed); }); @@ -414,13 +416,12 @@ extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { trace_scope!("windowWillEnterFullscreen:"); - INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); - with_state(this, |state| { state.with_window(|window| { let mut shared_state = window.lock_shared_state("window_will_enter_fullscreen"); shared_state.maximized = window.is_zoomed(); - match shared_state.fullscreen { + let fullscreen = shared_state.fullscreen.as_ref(); + match 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) @@ -445,8 +446,6 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) { trace_scope!("windowWillExitFullScreen:"); - INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); - with_state(this, |state| { state.with_window(|window| { let mut shared_state = window.lock_shared_state("window_will_exit_fullscreen"); @@ -490,8 +489,6 @@ extern "C" fn window_will_use_fullscreen_presentation_options( /// Invoked when entered fullscreen extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { trace_scope!("windowDidEnterFullscreen:"); - INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst); - with_state(this, |state| { state.initial_fullscreen = false; state.with_window(|window| { @@ -509,7 +506,6 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { /// Invoked when exited fullscreen extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { trace_scope!("windowDidExitFullscreen:"); - INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst); with_state(this, |state| { state.with_window(|window| { @@ -550,15 +546,30 @@ extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) shared_state.target_fullscreen = None; }); if state.initial_fullscreen { - let _: () = unsafe { - msg_send![*state.ns_window, + unsafe { + let _: () = msg_send![*state.ns_window, performSelector:sel!(toggleFullScreen:) withObject:nil afterDelay: 0.5 - ] + ]; }; } else { state.with_window(|window| window.restore_state_from_fullscreen()); } }); } + +// Invoked when the occlusion state of the window changes +extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) { + trace_scope!("windowDidChangeOcclusionState:"); + unsafe { + with_state(this, |state| { + state.emit_event(WindowEvent::Occluded( + !state + .ns_window + .occlusionState() + .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), + )) + }); + } +} diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index d193094ea7..6bec70c972 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -16,7 +16,7 @@ pub struct EventLoop { elw: RootEventLoopWindowTarget, } -#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} impl EventLoop { @@ -29,7 +29,22 @@ impl EventLoop { } } - pub fn run(self, mut event_handler: F) -> ! + pub fn run(self, event_handler: F) -> ! + where + F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + self.spawn(event_handler); + + // Throw an exception to break out of Rust execution and use unreachable to tell the + // compiler this function won't return, giving it a return type of '!' + backend::throw( + "Using exceptions for control flow, don't mind me. This isn't actually an error!", + ); + + unreachable!(); + } + + pub fn spawn(self, mut event_handler: F) where F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), { @@ -41,14 +56,6 @@ impl EventLoop { self.elw.p.run(Box::new(move |event, flow| { event_handler(event, &target, flow) })); - - // Throw an exception to break out of Rust exceution and use unreachable to tell the - // compiler this function won't return, giving it a return type of '!' - backend::throw( - "Using exceptions for control flow, don't mind me. This isn't actually an error!", - ); - - unreachable!(); } pub fn create_proxy(&self) -> EventLoopProxy { diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index d476a4eeeb..55acc1ec1c 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -65,7 +65,7 @@ impl Runner { } } - /// Returns the cooresponding `StartCause` for the current `state`, or `None` + /// Returns the corresponding `StartCause` for the current `state`, or `None` /// when in `Exit` state. fn maybe_start_cause(&self) -> Option { Some(match self.state { @@ -158,8 +158,9 @@ impl Shared { } pub fn init(&self) { - let start_cause = Event::NewEvents(StartCause::Init); - self.run_until_cleared(iter::once(start_cause)); + // NB: For consistency all platforms must emit a 'resumed' event even though web + // applications don't themselves have a formal suspend/resume lifecycle. + self.run_until_cleared([Event::NewEvents(StartCause::Init), Event::Resumed].into_iter()); } // Run the polling logic for the Poll ControlFlow, which involves clearing the queue @@ -456,11 +457,8 @@ impl Shared { ControlFlow::ExitWithCode(_) => State::Exit, }; - match *self.0.runner.borrow_mut() { - RunnerEnum::Running(ref mut runner) => { - runner.state = new_state; - } - _ => (), + if let RunnerEnum::Running(ref mut runner) = *self.0.runner.borrow_mut() { + runner.state = new_state; } } diff --git a/src/platform_impl/web/event_loop/state.rs b/src/platform_impl/web/event_loop/state.rs index 16b6e6232c..d7c7fb65c6 100644 --- a/src/platform_impl/web/event_loop/state.rs +++ b/src/platform_impl/web/event_loop/state.rs @@ -22,10 +22,7 @@ pub enum State { impl State { pub fn is_exit(&self) -> bool { - match self { - State::Exit => true, - _ => false, - } + matches!(self, State::Exit) } pub fn control_flow(&self) -> ControlFlow { diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 0ed7c37ace..6b15e3dd22 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,3 +1,10 @@ +use std::cell::RefCell; +use std::clone::Clone; +use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; +use std::rc::Rc; + +use raw_window_handle::{RawDisplayHandle, WebDisplayHandle}; + use super::{ super::monitor::MonitorHandle, backend, device::DeviceId, proxy::EventLoopProxy, runner, window::WindowId, @@ -10,10 +17,6 @@ use crate::event::{ use crate::event_loop::ControlFlow; use crate::monitor::MonitorHandle as RootMH; use crate::window::{Theme, WindowId as RootWindowId}; -use std::cell::RefCell; -use std::clone::Clone; -use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; -use std::rc::Rc; pub struct EventLoopWindowTarget { pub(crate) runner: runner::Shared, @@ -50,7 +53,12 @@ impl EventLoopWindowTarget { WindowId(self.runner.generate_id()) } - pub fn register(&self, canvas: &Rc>, id: WindowId) { + pub fn register( + &self, + canvas: &Rc>, + id: WindowId, + prevent_default: bool, + ) { self.runner.add_canvas(RootWindowId(id), canvas); let mut canvas = canvas.borrow_mut(); canvas.set_attribute("data-raw-handle", &id.0.to_string()); @@ -72,48 +80,57 @@ impl EventLoopWindowTarget { }); let runner = self.runner.clone(); - canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Pressed, - virtual_keycode, - modifiers, + canvas.on_keyboard_press( + move |scancode, virtual_keycode, modifiers| { + #[allow(deprecated)] + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode, + state: ElementState::Pressed, + virtual_keycode, + modifiers, + }, + is_synthetic: false, }, - is_synthetic: false, - }, - }); - }); + }); + }, + prevent_default, + ); let runner = self.runner.clone(); - canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Released, - virtual_keycode, - modifiers, + canvas.on_keyboard_release( + move |scancode, virtual_keycode, modifiers| { + #[allow(deprecated)] + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode, + state: ElementState::Released, + virtual_keycode, + modifiers, + }, + is_synthetic: false, }, - is_synthetic: false, - }, - }); - }); + }); + }, + prevent_default, + ); let runner = self.runner.clone(); - canvas.on_received_character(move |char_code| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ReceivedCharacter(char_code), - }); - }); + canvas.on_received_character( + move |char_code| { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ReceivedCharacter(char_code), + }); + }, + prevent_default, + ); let runner = self.runner.clone(); canvas.on_cursor_leave(move |pointer_id| { @@ -160,13 +177,17 @@ impl EventLoopWindowTarget { // user code has the correct cursor position. runner.send_events( std::iter::once(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(true), + }) + .chain(std::iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id: RootDeviceId(DeviceId(pointer_id)), position, modifiers, }, - }) + })) .chain(std::iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { @@ -193,17 +214,20 @@ impl EventLoopWindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseWheel { - device_id: RootDeviceId(DeviceId(pointer_id)), - delta, - phase: TouchPhase::Moved, - modifiers, - }, - }); - }); + canvas.on_mouse_wheel( + move |pointer_id, delta, modifiers| { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseWheel { + device_id: RootDeviceId(DeviceId(pointer_id)), + delta, + phase: TouchPhase::Moved, + modifiers, + }, + }); + }, + prevent_default, + ); let runner = self.runner.clone(); let raw = canvas.raw().clone(); @@ -258,4 +282,8 @@ impl EventLoopWindowTarget { inner: MonitorHandle, }) } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Web(WebDisplayHandle::empty()) + } } diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index 780ffccd0a..5bbcc44319 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -17,6 +17,10 @@ impl MonitorHandle { None } + pub fn refresh_rate_millihertz(&self) -> Option { + None + } + pub fn size(&self) -> PhysicalSize { PhysicalSize { width: 0, @@ -41,8 +45,8 @@ impl VideoMode { unimplemented!(); } - pub fn refresh_rate(&self) -> u16 { - 32 + pub fn refresh_rate_millihertz(&self) -> u32 { + 32000 } pub fn monitor(&self) -> RootMonitorHandle { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 8d68e5aaab..ebae672227 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -44,11 +44,11 @@ impl Canvas { Some(canvas) => canvas, None => { let window = web_sys::window() - .ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?; + .ok_or_else(|| os_error!(OsError("Failed to obtain window".to_owned())))?; let document = window .document() - .ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?; + .ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?; document .create_element("canvas") @@ -62,9 +62,11 @@ impl Canvas { // sequential keyboard navigation, but its order is defined by the // document's source order. // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex - canvas - .set_attribute("tabindex", "0") - .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; + if attr.focusable { + canvas + .set_attribute("tabindex", "0") + .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; + } let mouse_state = if has_pointer_event() { MouseState::HasPointerEvent(pointer_handler::PointerHandler::new()) @@ -89,15 +91,15 @@ impl Canvas { }) } - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), RootOE> { - if grab { + pub fn set_cursor_lock(&self, lock: bool) -> Result<(), RootOE> { + if lock { self.raw().request_pointer_lock(); } else { let window = web_sys::window() - .ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?; + .ok_or_else(|| os_error!(OsError("Failed to obtain window".to_owned())))?; let document = window .document() - .ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?; + .ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?; document.exit_pointer_lock(); } Ok(()) @@ -107,7 +109,7 @@ impl Canvas { self.common .raw .set_attribute(attribute, value) - .expect(&format!("Set attribute: {}", attribute)); + .unwrap_or_else(|err| panic!("error: {:?}\nSet attribute: {}", err, attribute)) } pub fn position(&self) -> LogicalPosition { @@ -148,14 +150,17 @@ impl Canvas { })); } - pub fn on_keyboard_release(&mut self, mut handler: F) + pub fn on_keyboard_release(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { self.on_keyboard_release = Some(self.common.add_user_event( "keyup", move |event: KeyboardEvent| { - event.prevent_default(); + if prevent_default { + event.prevent_default(); + } + handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -165,7 +170,7 @@ impl Canvas { )); } - pub fn on_keyboard_press(&mut self, mut handler: F) + pub fn on_keyboard_press(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { @@ -173,16 +178,19 @@ impl Canvas { "keydown", move |event: KeyboardEvent| { // event.prevent_default() would suppress subsequent on_received_character() calls. That - // supression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to + // suppression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to // scroll, etc. We should not do it for key sequences that result in meaningful character // input though. - let event_key = &event.key(); - let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); - let is_shortcut_modifiers = - (event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr"); - if !is_key_string || is_shortcut_modifiers { - event.prevent_default(); + if prevent_default { + let event_key = &event.key(); + let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); + let is_shortcut_modifiers = + (event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr"); + if !is_key_string || is_shortcut_modifiers { + event.prevent_default(); + } } + handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -192,7 +200,7 @@ impl Canvas { )); } - pub fn on_received_character(&mut self, mut handler: F) + pub fn on_received_character(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(char), { @@ -204,8 +212,11 @@ impl Canvas { self.on_received_character = Some(self.common.add_user_event( "keypress", move |event: KeyboardEvent| { - // Supress further handling to stop keys like the space key from scrolling the page. - event.prevent_default(); + // Suppress further handling to stop keys like the space key from scrolling the page. + if prevent_default { + event.prevent_default(); + } + handler(event::codepoint(&event)); }, )); @@ -261,12 +272,15 @@ impl Canvas { } } - pub fn on_mouse_wheel(&mut self, mut handler: F) + pub fn on_mouse_wheel(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { - event.prevent_default(); + if prevent_default { + event.prevent_default(); + } + if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); } @@ -339,9 +353,7 @@ impl Common { handler(event); }) as Box); - let listener = EventListenerHandle::new(&self.raw, event_name, closure); - - listener + EventListenerHandle::new(&self.raw, event_name, closure) } // The difference between add_event and add_user_event is that the latter has a special meaning diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs index 81e2aead02..1effb455c7 100644 --- a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -8,6 +8,8 @@ use std::rc::Rc; use web_sys::{EventTarget, MouseEvent}; +type MouseLeaveHandler = Rc>>>; + #[allow(dead_code)] pub(super) struct MouseHandler { on_mouse_leave: Option>, @@ -15,7 +17,7 @@ pub(super) struct MouseHandler { on_mouse_move: Option>, on_mouse_press: Option>, on_mouse_release: Option>, - on_mouse_leave_handler: Rc>>>, + on_mouse_leave_handler: MouseLeaveHandler, mouse_capture_state: Rc>, } @@ -175,8 +177,8 @@ impl MouseHandler { .map_or(false, |target| target == EventTarget::from(canvas.clone())); match &*mouse_capture_state { // Don't handle hover events outside of canvas. - MouseCaptureState::NotCaptured if !is_over_canvas => return, - MouseCaptureState::OtherElement if !is_over_canvas => return, + MouseCaptureState::NotCaptured | MouseCaptureState::OtherElement + if !is_over_canvas => {} // If hovering over the canvas, just send the cursor move event. MouseCaptureState::NotCaptured | MouseCaptureState::OtherElement diff --git a/src/platform_impl/web/web_sys/media_query_handle.rs b/src/platform_impl/web/web_sys/media_query_handle.rs index c2ab40da77..dc9595617c 100644 --- a/src/platform_impl/web/web_sys/media_query_handle.rs +++ b/src/platform_impl/web/web_sys/media_query_handle.rs @@ -17,7 +17,7 @@ impl MediaQueryListHandle { .ok() .flatten() .and_then(|mql| { - mql.add_listener_with_opt_callback(Some(&listener.as_ref().unchecked_ref())) + mql.add_listener_with_opt_callback(Some(listener.as_ref().unchecked_ref())) .map(|_| mql) .ok() }); diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 8057f7756a..5e2d6c3820 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -98,7 +98,7 @@ pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: let style = raw.style(); style .set_property(property, value) - .expect(&format!("Failed to set {}", property)); + .unwrap_or_else(|err| panic!("error: {:?}\nFailed to set {}", err, property)) } pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool { diff --git a/src/platform_impl/web/web_sys/scaling.rs b/src/platform_impl/web/web_sys/scaling.rs index 08b52ae306..7eb82c9fb5 100644 --- a/src/platform_impl/web/web_sys/scaling.rs +++ b/src/platform_impl/web/web_sys/scaling.rs @@ -65,13 +65,13 @@ impl ScaleChangeDetectorInternal { ); let mql = MediaQueryListHandle::new(&media_query, closure); if let Some(mql) = &mql { - assert_eq!(mql.mql().matches(), true); + assert!(mql.mql().matches()); } mql } fn handler(&mut self, event: MediaQueryListEvent) { - assert_eq!(event.matches(), false); + assert!(!event.matches()); let mql = self .mql .take() diff --git a/src/platform_impl/web/web_sys/timeout.rs b/src/platform_impl/web/web_sys/timeout.rs index e95c54ed51..1978cbb38d 100644 --- a/src/platform_impl/web/web_sys/timeout.rs +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -21,7 +21,7 @@ impl Timeout { let handle = window .set_timeout_with_callback_and_timeout_and_arguments_0( - &closure.as_ref().unchecked_ref(), + closure.as_ref().unchecked_ref(), duration.as_millis() as i32, ) .expect("Failed to set timeout"); @@ -64,7 +64,7 @@ impl AnimationFrameRequest { }) as Box); let handle = window - .request_animation_frame(&closure.as_ref().unchecked_ref()) + .request_animation_frame(closure.as_ref().unchecked_ref()) .expect("Failed to request animation frame"); AnimationFrameRequest { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 6f5201b222..492baefc16 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -4,10 +4,10 @@ use crate::event; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMH; use crate::window::{ - CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI, + CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI, }; -use raw_window_handle::{RawWindowHandle, WebHandle}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle}; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget}; @@ -26,7 +26,7 @@ pub struct Window { } impl Window { - pub fn new( + pub(crate) fn new( target: &EventLoopWindowTarget, attr: WindowAttributes, platform_attr: PlatformSpecificWindowBuilderAttributes, @@ -35,12 +35,14 @@ impl Window { let id = target.generate_id(); + let prevent_default = platform_attr.prevent_default; + let canvas = backend::Canvas::create(platform_attr)?; - let mut canvas = Rc::new(RefCell::new(canvas)); + let canvas = Rc::new(RefCell::new(canvas)); let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); - target.register(&mut canvas, id); + target.register(&canvas, id, prevent_default); let runner = target.runner.clone(); let resize_notify_fn = Box::new(move |new_size| { @@ -77,7 +79,7 @@ impl Window { Ok(window) } - pub fn canvas<'a>(&'a self) -> Ref<'a, backend::Canvas> { + pub fn canvas(&self) -> Ref<'_, backend::Canvas> { self.canvas.borrow() } @@ -216,11 +218,19 @@ impl Window { } #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + let lock = match mode { + CursorGrabMode::None => false, + CursorGrabMode::Locked => true, + CursorGrabMode::Confined => { + return Err(ExternalError::NotSupported(NotSupportedError::new())) + } + }; + self.canvas .borrow() - .set_cursor_grab(grab) - .map_err(|e| ExternalError::Os(e)) + .set_cursor_lock(lock) + .map_err(ExternalError::Os) } #[inline] @@ -344,14 +354,19 @@ impl Window { #[inline] pub fn id(&self) -> WindowId { - return self.id; + self.id } #[inline] pub fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = WebHandle::empty(); - handle.id = self.id.0; - RawWindowHandle::Web(handle) + let mut window_handle = WebWindowHandle::empty(); + window_handle.id = self.id.0; + RawWindowHandle::Web(window_handle) + } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Web(WebDisplayHandle::empty()) } } @@ -372,7 +387,31 @@ impl WindowId { } } -#[derive(Default, Clone)] +impl From for u64 { + fn from(window_id: WindowId) -> Self { + window_id.0 as u64 + } +} + +impl From for WindowId { + fn from(raw_id: u64) -> Self { + Self(raw_id as u32) + } +} + +#[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub(crate) canvas: Option, + pub(crate) prevent_default: bool, + pub(crate) focusable: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + Self { + canvas: None, + prevent_default: true, + focusable: true, + } + } } diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index 8e1deac3ae..6487617f2c 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -2,6 +2,7 @@ /// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode use std::{ffi::c_void, ptr}; +use once_cell::sync::Lazy; use windows_sys::{ core::PCSTR, Win32::{ @@ -22,47 +23,45 @@ use crate::window::Theme; use super::util; -lazy_static! { - static ref WIN10_BUILD_VERSION: Option = { - type RtlGetVersion = unsafe extern "system" fn (*mut OSVERSIONINFOW) -> NTSTATUS; - let handle = get_function!("ntdll.dll", RtlGetVersion); - - if let Some(rtl_get_version) = handle { - unsafe { - let mut vi = OSVERSIONINFOW { - dwOSVersionInfoSize: 0, - dwMajorVersion: 0, - dwMinorVersion: 0, - dwBuildNumber: 0, - dwPlatformId: 0, - szCSDVersion: [0; 128], - }; - - let status = (rtl_get_version)(&mut vi); - - if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { - Some(vi.dwBuildNumber) - } else { - None - } - } - } else { - None - } - }; +static WIN10_BUILD_VERSION: Lazy> = Lazy::new(|| { + type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS; + let handle = get_function!("ntdll.dll", RtlGetVersion); + + if let Some(rtl_get_version) = handle { + unsafe { + let mut vi = OSVERSIONINFOW { + dwOSVersionInfoSize: 0, + dwMajorVersion: 0, + dwMinorVersion: 0, + dwBuildNumber: 0, + dwPlatformId: 0, + szCSDVersion: [0; 128], + }; + + let status = (rtl_get_version)(&mut vi); - static ref DARK_MODE_SUPPORTED: bool = { - // We won't try to do anything for windows versions < 17763 - // (Windows 10 October 2018 update) - match *WIN10_BUILD_VERSION { - Some(v) => v >= 17763, - None => false + if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { + Some(vi.dwBuildNumber) + } else { + None + } } - }; + } else { + None + } +}); + +static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { + // We won't try to do anything for windows versions < 17763 + // (Windows 10 October 2018 update) + match *WIN10_BUILD_VERSION { + Some(v) => v >= 17763, + None => false, + } +}); - static ref DARK_THEME_NAME: Vec = util::encode_wide("DarkMode_Explorer"); - static ref LIGHT_THEME_NAME: Vec = util::encode_wide(""); -} +static DARK_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("DarkMode_Explorer")); +static LIGHT_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("")); /// Attempt to set a theme on a window, if necessary. /// Returns the theme that was picked @@ -113,10 +112,8 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { cbData: usize, } - lazy_static! { - static ref SET_WINDOW_COMPOSITION_ATTRIBUTE: Option = - get_function!("user32.dll", SetWindowCompositionAttribute); - } + static SET_WINDOW_COMPOSITION_ATTRIBUTE: Lazy> = + Lazy::new(|| get_function!("user32.dll", SetWindowCompositionAttribute)); if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE { unsafe { @@ -144,23 +141,19 @@ fn should_use_dark_mode() -> bool { fn should_apps_use_dark_mode() -> bool { type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; - lazy_static! { - static ref SHOULD_APPS_USE_DARK_MODE: Option = { - unsafe { - const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR; + static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { + const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR; - let module = LoadLibraryA("uxtheme.dll\0".as_ptr()); + let module = LoadLibraryA("uxtheme.dll\0".as_ptr()); - if module == 0 { - return None; - } + if module == 0 { + return None; + } - let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL); + let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL); - handle.map(|handle| std::mem::transmute(handle)) - } - }; - } + handle.map(|handle| std::mem::transmute(handle)) + }); SHOULD_APPS_USE_DARK_MODE .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) diff --git a/src/platform_impl/windows/drop_handler.rs b/src/platform_impl/windows/drop_handler.rs index f273ad0036..2db12a67a8 100644 --- a/src/platform_impl/windows/drop_handler.rs +++ b/src/platform_impl/windows/drop_handler.rs @@ -80,7 +80,7 @@ impl FileDropHandler { let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1; if count == 0 { // Destroy the underlying data - Box::from_raw(drop_handler as *mut FileDropHandlerData); + drop(Box::from_raw(drop_handler as *mut FileDropHandlerData)); } count as u32 } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index b3e9826649..632fc854f9 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -2,7 +2,6 @@ mod runner; -use parking_lot::Mutex; use std::{ cell::Cell, collections::VecDeque, @@ -18,6 +17,10 @@ use std::{ time::{Duration, Instant}, }; +use once_cell::sync::Lazy; +use parking_lot::Mutex; +use raw_window_handle::{RawDisplayHandle, WindowsDisplayHandle}; + use windows_sys::Win32::{ Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE, Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_TIMEOUT, WPARAM}, @@ -60,7 +63,7 @@ use windows_sys::Win32::{ WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, - WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCCREATE, WM_NCDESTROY, + WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, @@ -92,6 +95,8 @@ use crate::{ }; use runner::{EventLoopRunner, EventLoopRunnerShared}; +use super::window::set_skip_taskbar; + type GetPointerFrameInfoHistory = unsafe extern "system" fn( pointerId: u32, entriesCount: *mut u32, @@ -112,18 +117,17 @@ type GetPointerTouchInfo = type GetPointerPenInfo = unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; -lazy_static! { - static ref GET_POINTER_FRAME_INFO_HISTORY: Option = - get_function!("user32.dll", GetPointerFrameInfoHistory); - static ref SKIP_POINTER_FRAME_MESSAGES: Option = - get_function!("user32.dll", SkipPointerFrameMessages); - static ref GET_POINTER_DEVICE_RECTS: Option = - get_function!("user32.dll", GetPointerDeviceRects); - static ref GET_POINTER_TOUCH_INFO: Option = - get_function!("user32.dll", GetPointerTouchInfo); - static ref GET_POINTER_PEN_INFO: Option = - get_function!("user32.dll", GetPointerPenInfo); -} +static GET_POINTER_FRAME_INFO_HISTORY: Lazy> = + Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); +static SKIP_POINTER_FRAME_MESSAGES: Lazy> = + Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); +static GET_POINTER_DEVICE_RECTS: Lazy> = + Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects)); +static GET_POINTER_TOUCH_INFO: Lazy> = + Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo)); +static GET_POINTER_PEN_INFO: Lazy> = + Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo)); + pub(crate) struct WindowData { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, @@ -314,6 +318,10 @@ impl EventLoopWindowTarget { let monitor = monitor::primary_monitor(); Some(RootMonitorHandle { inner: monitor }) } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Windows(WindowsDisplayHandle::empty()) + } } /// Returns the id of the main thread. @@ -333,7 +341,7 @@ impl EventLoopWindowTarget { /// entrypoint. /// /// Full details of CRT initialization can be found here: -/// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-160 +/// fn main_thread_id() -> u32 { static mut MAIN_THREAD_ID: u32 = 0; @@ -375,19 +383,17 @@ fn get_wait_thread_id() -> u32 { } } -lazy_static! { - static ref WAIT_PERIOD_MIN: Option = unsafe { - let mut caps = TIMECAPS { - wPeriodMin: 0, - wPeriodMax: 0, - }; - if timeGetDevCaps(&mut caps, mem::size_of::() as u32) == TIMERR_NOERROR { - Some(caps.wPeriodMin) - } else { - None - } +static WAIT_PERIOD_MIN: Lazy> = Lazy::new(|| unsafe { + let mut caps = TIMECAPS { + wPeriodMin: 0, + wPeriodMax: 0, }; -} + if timeGetDevCaps(&mut caps, mem::size_of::() as u32) == TIMERR_NOERROR { + Some(caps.wPeriodMin) + } else { + None + } +}); fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) { unsafe { @@ -582,59 +588,40 @@ impl EventLoopProxy { type WaitUntilInstantBox = Box; -lazy_static! { - // Message sent by the `EventLoopProxy` when we want to wake up the thread. - // WPARAM and LPARAM are unused. - static ref USER_EVENT_MSG_ID: u32 = { - unsafe { - RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr()) - } - }; - // Message sent when we want to execute a closure in the thread. - // WPARAM contains a Box> that must be retrieved with `Box::from_raw`, - // and LPARAM is unused. - static ref EXEC_MSG_ID: u32 = { - unsafe { - RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr()) - } - }; - static ref PROCESS_NEW_EVENTS_MSG_ID: u32 = { - unsafe { - RegisterWindowMessageA("Winit::ProcessNewEvents\0".as_ptr()) - } - }; - /// lparam is the wait thread's message id. - static ref SEND_WAIT_THREAD_ID_MSG_ID: u32 = { - unsafe { - RegisterWindowMessageA("Winit::SendWaitThreadId\0".as_ptr()) - } - }; - /// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should - /// be sent. - static ref WAIT_UNTIL_MSG_ID: u32 = { - unsafe { - RegisterWindowMessageA("Winit::WaitUntil\0".as_ptr()) - } - }; - static ref CANCEL_WAIT_UNTIL_MSG_ID: u32 = { - unsafe { - RegisterWindowMessageA("Winit::CancelWaitUntil\0".as_ptr()) - } - }; - // Message sent by a `Window` when it wants to be destroyed by the main thread. - // WPARAM and LPARAM are unused. - pub static ref DESTROY_MSG_ID: u32 = { - unsafe { - RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr()) - } - }; - // WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the - // documentation in the `window_state` module for more information. - pub static ref SET_RETAIN_STATE_ON_SIZE_MSG_ID: u32 = unsafe { - RegisterWindowMessageA("Winit::SetRetainMaximized\0".as_ptr()) - }; - static ref THREAD_EVENT_TARGET_WINDOW_CLASS: Vec = util::encode_wide("Winit Thread Event Target"); -} +// Message sent by the `EventLoopProxy` when we want to wake up the thread. +// WPARAM and LPARAM are unused. +static USER_EVENT_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr()) }); +// Message sent when we want to execute a closure in the thread. +// WPARAM contains a Box> that must be retrieved with `Box::from_raw`, +// and LPARAM is unused. +static EXEC_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr()) }); +static PROCESS_NEW_EVENTS_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::ProcessNewEvents\0".as_ptr()) }); +/// lparam is the wait thread's message id. +static SEND_WAIT_THREAD_ID_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::SendWaitThreadId\0".as_ptr()) }); +/// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should +/// be sent. +static WAIT_UNTIL_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::WaitUntil\0".as_ptr()) }); +static CANCEL_WAIT_UNTIL_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::CancelWaitUntil\0".as_ptr()) }); +// Message sent by a `Window` when it wants to be destroyed by the main thread. +// WPARAM and LPARAM are unused. +pub static DESTROY_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr()) }); +// WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the +// documentation in the `window_state` module for more information. +pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::SetRetainMaximized\0".as_ptr()) }); +static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy> = + Lazy::new(|| util::encode_wide("Winit Thread Event Target")); +/// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then broadcasts this message to all top-level windows +/// +pub static TASKBAR_CREATED: Lazy = + Lazy::new(|| unsafe { RegisterWindowMessageA("TaskbarCreated\0".as_ptr()) }); fn create_event_target_window() -> HWND { unsafe { @@ -820,6 +807,74 @@ fn update_modifiers(window: HWND, userdata: &WindowData) { } } +unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { + use crate::event::{ElementState::Released, WindowEvent::Focused}; + for windows_keycode in event::get_pressed_keys() { + let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); + let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + + update_modifiers(window, userdata); + + #[allow(deprecated)] + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }, + is_synthetic: true, + }, + }) + } + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: Focused(true), + }); +} + +unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { + use crate::event::{ + ElementState::Released, + ModifiersState, + WindowEvent::{Focused, ModifiersChanged}, + }; + for windows_keycode in event::get_pressed_keys() { + let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); + let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + + #[allow(deprecated)] + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }, + is_synthetic: true, + }, + }) + } + + userdata.window_state.lock().modifiers_state = ModifiersState::empty(); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ModifiersChanged(ModifiersState::empty()), + }); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: Focused(false), + }); +} + /// 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. // @@ -880,7 +935,7 @@ pub(super) unsafe extern "system" fn public_window_callback( }; if userdata_removed && recurse_depth == 0 { - Box::from_raw(userdata_ptr); + drop(Box::from_raw(userdata_ptr)); } result @@ -1795,74 +1850,32 @@ unsafe fn public_window_callback_inner( 0 } - WM_SETFOCUS => { - use crate::event::{ElementState::Released, WindowEvent::Focused}; - for windows_keycode in event::get_pressed_keys() { - let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - update_modifiers(window, userdata); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) + WM_NCACTIVATE => { + let is_active = wparam == 1; + let active_focus_changed = userdata.window_state.lock().set_active(is_active); + if active_focus_changed { + if is_active { + gain_active_focus(window, userdata); + } else { + lose_active_focus(window, userdata); + } } + DefWindowProcW(window, msg, wparam, lparam) + } - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: Focused(true), - }); - + WM_SETFOCUS => { + let active_focus_changed = userdata.window_state.lock().set_focused(true); + if active_focus_changed { + gain_active_focus(window, userdata); + } 0 } WM_KILLFOCUS => { - use crate::event::{ - ElementState::Released, - ModifiersState, - WindowEvent::{Focused, ModifiersChanged}, - }; - for windows_keycode in event::get_pressed_keys() { - let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) + let active_focus_changed = userdata.window_state.lock().set_focused(false); + if active_focus_changed { + lose_active_focus(window, userdata); } - - userdata.window_state.lock().modifiers_state = ModifiersState::empty(); - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ModifiersChanged(ModifiersState::empty()), - }); - - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: Focused(false), - }); 0 } @@ -2001,7 +2014,7 @@ unsafe fn public_window_callback_inner( false => old_physical_inner_size, }; - let _ = userdata.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ScaleFactorChanged { scale_factor: new_scale_factor, @@ -2153,7 +2166,7 @@ unsafe fn public_window_callback_inner( if window_state.current_theme != new_theme { window_state.current_theme = new_theme; - mem::drop(window_state); + drop(window_state); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ThemeChanged(new_theme), @@ -2174,6 +2187,10 @@ unsafe fn public_window_callback_inner( f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0) }); 0 + } else if msg == *TASKBAR_CREATED { + let window_state = userdata.window_state.lock(); + set_skip_taskbar(window, window_state.skip_taskbar); + DefWindowProcW(window, msg, wparam, lparam) } else { DefWindowProcW(window, msg, wparam, lparam) } @@ -2297,7 +2314,8 @@ unsafe extern "system" fn thread_event_target_callback( let mouse_button_flags = mouse.Anonymous.Anonymous.usButtonFlags; if util::has_flag(mouse_button_flags as u32, RI_MOUSE_WHEEL) { - let delta = mouse_button_flags as i16 as f32 / WHEEL_DELTA as f32; + let delta = mouse.Anonymous.Anonymous.usButtonData as i16 as f32 + / WHEEL_DELTA as f32; userdata.send_event(Event::DeviceEvent { device_id, event: MouseWheel { @@ -2411,7 +2429,7 @@ unsafe extern "system" fn thread_event_target_callback( .catch_unwind(callback) .unwrap_or(-1); if userdata_removed { - mem::drop(userdata); + drop(userdata); } else { Box::into_raw(userdata); } diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index fab5c61360..af03005557 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -393,6 +393,11 @@ impl EventLoopRunner { } }; self.call_event_handler(Event::NewEvents(start_cause)); + // NB: For consistency all platforms must emit a 'resumed' event even though Windows + // applications don't themselves have a formal suspend/resume lifecycle. + if init { + self.call_event_handler(Event::Resumed); + } self.dispatch_buffered_events(); RedrawWindow(self.thread_msg_target, ptr::null(), 0, RDW_INTERNALPAINT); } diff --git a/src/platform_impl/windows/ime.rs b/src/platform_impl/windows/ime.rs index d6c5c79b27..6e6d31ea5e 100644 --- a/src/platform_impl/windows/ime.rs +++ b/src/platform_impl/windows/ime.rs @@ -80,19 +80,18 @@ impl ImeContext { let data = self.get_composition_data(gcs_mode)?; let (prefix, shorts, suffix) = data.align_to::(); if prefix.is_empty() && suffix.is_empty() { - OsString::from_wide(&shorts).into_string().ok() + OsString::from_wide(shorts).into_string().ok() } else { None } } unsafe fn get_composition_data(&self, gcs_mode: u32) -> Option> { - let size = ImmGetCompositionStringW(self.himc, gcs_mode, null_mut(), 0); - if size < 0 { - return None; - } else if size == 0 { - return Some(Vec::new()); - } + let size = match ImmGetCompositionStringW(self.himc, gcs_mode, null_mut(), 0) { + 0 => return Some(Vec::new()), + size if size < 0 => return None, + size => size, + }; let mut buf = Vec::::with_capacity(size as _); let size = ImmGetCompositionStringW( @@ -139,7 +138,7 @@ impl ImeContext { } unsafe fn system_has_ime() -> bool { - return GetSystemMetrics(SM_IMMENABLED) != 0; + GetSystemMetrics(SM_IMMENABLED) != 0 } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 85830bc586..84bf917dc0 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -100,6 +100,18 @@ impl WindowId { } } +impl From for u64 { + fn from(window_id: WindowId) -> Self { + window_id.0 as u64 + } +} + +impl From for WindowId { + fn from(raw_id: u64) -> Self { + Self(raw_id as HWND) + } +} + #[inline(always)] const fn get_xbutton_wparam(x: u32) -> u16 { loword(x) diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 595c75efe3..469059560c 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -9,8 +9,8 @@ use windows_sys::Win32::{ Graphics::Gdi::{ EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint, MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, - DM_PELSWIDTH, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST, - MONITOR_DEFAULTTOPRIMARY, + DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, + MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY, }, }; @@ -29,7 +29,7 @@ use crate::{ pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, + pub(crate) refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, // DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen pub(crate) native_video_mode: Box, @@ -39,7 +39,7 @@ impl PartialEq for VideoMode { fn eq(&self, other: &Self) -> bool { self.size == other.size && self.bit_depth == other.bit_depth - && self.refresh_rate == other.refresh_rate + && self.refresh_rate_millihertz == other.refresh_rate_millihertz && self.monitor == other.monitor } } @@ -50,7 +50,7 @@ impl std::hash::Hash for VideoMode { fn hash(&self, state: &mut H) { self.size.hash(state); self.bit_depth.hash(state); - self.refresh_rate.hash(state); + self.refresh_rate_millihertz.hash(state); self.monitor.hash(state); } } @@ -60,7 +60,7 @@ impl std::fmt::Debug for VideoMode { f.debug_struct("VideoMode") .field("size", &self.size) .field("bit_depth", &self.bit_depth) - .field("refresh_rate", &self.refresh_rate) + .field("refresh_rate_millihertz", &self.refresh_rate_millihertz) .field("monitor", &self.monitor) .finish() } @@ -75,8 +75,8 @@ impl VideoMode { self.bit_depth } - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate + pub fn refresh_rate_millihertz(&self) -> u32 { + self.refresh_rate_millihertz } pub fn monitor(&self) -> RootMonitorHandle { @@ -192,6 +192,23 @@ impl MonitorHandle { } } + #[inline] + pub fn refresh_rate_millihertz(&self) -> Option { + let monitor_info = get_monitor_info(self.0).unwrap(); + let device_name = monitor_info.szDevice.as_ptr(); + unsafe { + let mut mode: DEVMODEW = mem::zeroed(); + mode.dmSize = mem::size_of_val(&mode) as u16; + if EnumDisplaySettingsExW(device_name, ENUM_CURRENT_SETTINGS, &mut mode, 0) + == false.into() + { + None + } else { + Some(mode.dmDisplayFrequency * 1000) + } + } + } + #[inline] pub fn position(&self) -> PhysicalPosition { let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor; @@ -233,7 +250,7 @@ impl MonitorHandle { video_mode: VideoMode { size: (mode.dmPelsWidth, mode.dmPelsHeight), bit_depth: mode.dmBitsPerPel as u16, - refresh_rate: mode.dmDisplayFrequency as u16, + refresh_rate_millihertz: mode.dmDisplayFrequency as u32 * 1000, monitor: self.clone(), native_video_mode: Box::new(mode), }, diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index a327701ffa..fd74b674f6 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -9,6 +9,7 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; +use once_cell::sync::Lazy; use windows_sys::{ core::{HRESULT, PCWSTR}, Win32::{ @@ -298,19 +299,17 @@ pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( dpi: u32, ) -> BOOL; -lazy_static! { - pub static ref GET_DPI_FOR_WINDOW: Option = - get_function!("user32.dll", GetDpiForWindow); - pub static ref ADJUST_WINDOW_RECT_EX_FOR_DPI: Option = - get_function!("user32.dll", AdjustWindowRectExForDpi); - pub static ref GET_DPI_FOR_MONITOR: Option = - get_function!("shcore.dll", GetDpiForMonitor); - pub static ref ENABLE_NON_CLIENT_DPI_SCALING: Option = - get_function!("user32.dll", EnableNonClientDpiScaling); - pub static ref SET_PROCESS_DPI_AWARENESS_CONTEXT: Option = - get_function!("user32.dll", SetProcessDpiAwarenessContext); - pub static ref SET_PROCESS_DPI_AWARENESS: Option = - get_function!("shcore.dll", SetProcessDpiAwareness); - pub static ref SET_PROCESS_DPI_AWARE: Option = - get_function!("user32.dll", SetProcessDPIAware); -} +pub static GET_DPI_FOR_WINDOW: Lazy> = + Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); +pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy> = + Lazy::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); +pub static GET_DPI_FOR_MONITOR: Lazy> = + Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor)); +pub static ENABLE_NON_CLIENT_DPI_SCALING: Lazy> = + Lazy::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); +pub static SET_PROCESS_DPI_AWARENESS_CONTEXT: Lazy> = + Lazy::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); +pub static SET_PROCESS_DPI_AWARENESS: Lazy> = + Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); +pub static SET_PROCESS_DPI_AWARE: Lazy> = + Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware)); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 29c4d3898d..e63075a833 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -1,7 +1,9 @@ #![cfg(target_os = "windows")] use parking_lot::Mutex; -use raw_window_handle::{RawWindowHandle, Win32Handle}; +use raw_window_handle::{ + RawDisplayHandle, RawWindowHandle, Win32WindowHandle, WindowsDisplayHandle, +}; use std::{ cell::Cell, ffi::c_void, @@ -69,7 +71,7 @@ use crate::{ window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, Parent, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes}, + window::{CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -85,7 +87,7 @@ pub struct Window { } impl Window { - pub fn new( + pub(crate) fn new( event_loop: &EventLoopWindowTarget, w_attr: WindowAttributes, pl_attr: PlatformSpecificWindowBuilderAttributes, @@ -260,10 +262,15 @@ impl Window { #[inline] pub fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = Win32Handle::empty(); - handle.hwnd = self.window.0 as *mut _; - handle.hinstance = self.hinstance() as *mut _; - RawWindowHandle::Win32(handle) + let mut window_handle = Win32WindowHandle::empty(); + window_handle.hwnd = self.window.0 as *mut _; + window_handle.hinstance = self.hinstance() as *mut _; + RawWindowHandle::Win32(window_handle) + } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Windows(WindowsDisplayHandle::empty()) } #[inline] @@ -276,7 +283,15 @@ impl Window { } #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + let confine = match mode { + CursorGrabMode::None => false, + CursorGrabMode::Confined => true, + CursorGrabMode::Locked => { + return Err(ExternalError::NotSupported(NotSupportedError::new())) + } + }; + let window = self.window.clone(); let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); @@ -286,7 +301,7 @@ impl Window { let result = window_state .lock() .mouse - .set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab)) + .set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, confine)) .map_err(|e| ExternalError::Os(os_error!(e))); let _ = tx.send(result); }); @@ -672,39 +687,8 @@ impl Window { #[inline] pub fn set_skip_taskbar(&self, skip: bool) { - com_initialized(); - unsafe { - TASKBAR_LIST.with(|task_bar_list_ptr| { - let mut task_bar_list = task_bar_list_ptr.get(); - - if task_bar_list.is_null() { - let hr = CoCreateInstance( - &CLSID_TaskbarList, - ptr::null_mut(), - CLSCTX_ALL, - &IID_ITaskbarList, - &mut task_bar_list as *mut _ as *mut _, - ); - - let hr_init = (*(*task_bar_list).lpVtbl).HrInit; - - if hr != S_OK || hr_init(task_bar_list.cast()) != S_OK { - // In some old windows, the taskbar object could not be created, we just ignore it - return; - } - task_bar_list_ptr.set(task_bar_list) - } - - task_bar_list = task_bar_list_ptr.get(); - if skip { - let delete_tab = (*(*task_bar_list).lpVtbl).DeleteTab; - delete_tab(task_bar_list, self.window.0); - } else { - let add_tab = (*(*task_bar_list).lpVtbl).AddTab; - add_tab(task_bar_list, self.window.0); - } - }); - } + self.window_state.lock().skip_taskbar = skip; + unsafe { set_skip_taskbar(self.hwnd(), skip) }; } #[inline] @@ -895,10 +879,17 @@ impl<'a, T: 'static> InitData<'a, T> { win.set_fullscreen(attributes.fullscreen); force_window_active(win.window.0); } else { - let dimensions = attributes + let size = attributes .inner_size .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); - win.set_inner_size(dimensions); + let max_size = attributes + .max_inner_size + .unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into()); + let min_size = attributes + .min_inner_size + .unwrap_or_else(|| PhysicalSize::new(0, 0).into()); + let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor()); + win.set_inner_size(clamped_size); if attributes.maximized { // Need to set MAXIMIZED after setting `inner_size` as @@ -1091,6 +1082,40 @@ unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) { }) } +pub(crate) unsafe fn set_skip_taskbar(hwnd: HWND, skip: bool) { + com_initialized(); + TASKBAR_LIST.with(|task_bar_list_ptr| { + let mut task_bar_list = task_bar_list_ptr.get(); + + if task_bar_list.is_null() { + let hr = CoCreateInstance( + &CLSID_TaskbarList, + ptr::null_mut(), + CLSCTX_ALL, + &IID_ITaskbarList, + &mut task_bar_list as *mut _ as *mut _, + ); + + let hr_init = (*(*task_bar_list).lpVtbl).HrInit; + + if hr != S_OK || hr_init(task_bar_list.cast()) != S_OK { + // In some old windows, the taskbar object could not be created, we just ignore it + return; + } + task_bar_list_ptr.set(task_bar_list) + } + + task_bar_list = task_bar_list_ptr.get(); + if skip { + let delete_tab = (*(*task_bar_list).lpVtbl).DeleteTab; + delete_tab(task_bar_list, hwnd); + } else { + let add_tab = (*(*task_bar_list).lpVtbl).AddTab; + add_tab(task_bar_list, hwnd); + } + }); +} + unsafe fn force_window_active(handle: HWND) { // In some situation, calling SetForegroundWindow could not bring up the window, // This is a little hack which can "steal" the foreground window permission diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index ed71204f73..21841f07c0 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -45,6 +45,12 @@ pub struct WindowState { pub ime_state: ImeState, pub ime_allowed: bool, + + // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS + pub is_active: bool, + pub is_focused: bool, + + pub skip_taskbar: bool, } #[derive(Clone)] @@ -96,11 +102,10 @@ bitflags! { const MINIMIZED = 1 << 12; - const IGNORE_CURSOR_EVENT = 1 << 14; + const IGNORE_CURSOR_EVENT = 1 << 15; const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; - const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; } } @@ -112,7 +117,7 @@ pub enum ImeState { } impl WindowState { - pub fn new( + pub(crate) fn new( attributes: &WindowAttributes, taskbar_icon: Option, scale_factor: f64, @@ -145,6 +150,11 @@ impl WindowState { ime_state: ImeState::Disabled, ime_allowed: false, + + is_active: false, + is_focused: false, + + skip_taskbar: false, } } @@ -170,6 +180,24 @@ impl WindowState { { f(&mut self.window_flags); } + + pub fn has_active_focus(&self) -> bool { + self.is_active && self.is_focused + } + + // Updates is_active and returns whether active-focus state has changed + pub fn set_active(&mut self, is_active: bool) -> bool { + let old = self.has_active_focus(); + self.is_active = is_active; + old != self.has_active_focus() + } + + // Updates is_focused and returns whether active-focus state has changed + pub fn set_focused(&mut self, is_focused: bool) -> bool { + let old = self.has_active_focus(); + self.is_focused = is_focused; + old != self.has_active_focus() + } } impl MouseProperties { @@ -200,9 +228,6 @@ impl WindowFlags { if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) { self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; } - if !self.contains(WindowFlags::VISIBLE) { - self &= WindowFlags::INVISIBLE_AND_MASK; - } if !self.contains(WindowFlags::DECORATIONS) { self &= WindowFlags::NO_DECORATIONS_AND_MASK; } @@ -265,21 +290,17 @@ impl WindowFlags { new = new.mask(); let diff = self ^ new; + if diff == WindowFlags::empty() { return; } - if diff.contains(WindowFlags::VISIBLE) { + if new.contains(WindowFlags::VISIBLE) { unsafe { - ShowWindow( - window, - match new.contains(WindowFlags::VISIBLE) { - true => SW_SHOW, - false => SW_HIDE, - }, - ); + ShowWindow(window, SW_SHOW); } } + if diff.contains(WindowFlags::ALWAYS_ON_TOP) { unsafe { SetWindowPos( @@ -323,6 +344,12 @@ impl WindowFlags { } } + if !new.contains(WindowFlags::VISIBLE) { + unsafe { + ShowWindow(window, SW_HIDE); + } + } + if diff != WindowFlags::empty() { let (style, style_ex) = new.to_window_styles(); diff --git a/src/window.rs b/src/window.rs index 96189872f8..4e1cb18b7e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,6 +1,10 @@ -//! The `Window` struct and associated types. +//! The [`Window`] struct and associated types. use std::fmt; +use raw_window_handle::{ + HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, +}; + use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError}, @@ -61,7 +65,7 @@ impl Drop for Window { /// Identifier of a window. Unique for each window. /// -/// Can be obtained with `window.id()`. +/// Can be obtained with [`window.id()`](`Window::id`). /// /// Whenever you receive an event specific to a window, this event contains a `WindowId` which you /// can then compare to the ids of your windows. @@ -69,13 +73,13 @@ impl Drop for Window { pub struct WindowId(pub(crate) platform_impl::WindowId); impl WindowId { - /// Returns a dummy `WindowId`, useful for unit testing. + /// Returns a dummy id, useful for unit testing. /// /// # Safety /// /// The only guarantee made about the return value of this function is that /// it will always be equal to itself and to future values returned by this function. - /// No other guarantees are made. This may be equal to a real `WindowId`. + /// No other guarantees are made. This may be equal to a real [`WindowId`]. /// /// **Passing this into a winit function will result in undefined behavior.** pub const unsafe fn dummy() -> Self { @@ -83,11 +87,24 @@ impl WindowId { } } +impl From for u64 { + fn from(window_id: WindowId) -> Self { + window_id.0.into() + } +} + +impl From for WindowId { + fn from(raw_id: u64) -> Self { + Self(raw_id.into()) + } +} + /// Object that allows building windows. #[derive(Clone, Default)] +#[must_use] pub struct WindowBuilder { /// The attributes to use to create the window. - pub window: WindowAttributes, + pub(crate) window: WindowAttributes, // Platform-specific configuration. pub(crate) platform_specific: platform_impl::PlatformSpecificWindowBuilderAttributes, @@ -103,92 +120,19 @@ impl fmt::Debug for WindowBuilder { /// Attributes to use when creating a window. #[derive(Debug, Clone)] -pub struct WindowAttributes { - /// The dimensions of the window. If this is `None`, some platform-specific dimensions will be - /// used. - /// - /// The default is `None`. +pub(crate) struct WindowAttributes { pub inner_size: Option, - - /// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved). - /// - /// The default is `None`. pub min_inner_size: Option, - - /// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform. - /// - /// The default is `None`. pub max_inner_size: Option, - - /// The desired position of the window. If this is `None`, some platform-specific position - /// will be chosen. - /// - /// The default is `None`. - /// - /// ## Platform-specific - /// - /// - **macOS**: The top left corner position of the window content, the window's "inner" - /// position. The window title bar will be placed above it. - /// The window will be positioned such that it fits on screen, maintaining - /// set `inner_size` if any. - /// If you need to precisely position the top left corner of the whole window you have to - /// use [`Window::set_outer_position`] after creating the window. - /// - **Windows**: The top left corner position of the window title bar, the window's "outer" - /// position. - /// There may be a small gap between this position and the window due to the specifics of the - /// Window Manager. - /// - **X11**: The top left corner of the window, the window's "outer" position. - /// - **Others**: Ignored. - /// - /// See [`Window::set_outer_position`]. - /// - /// [`Window::set_outer_position`]: crate::window::Window::set_outer_position pub position: Option, - - /// Whether the window is resizable or not. - /// - /// The default is `true`. pub resizable: bool, - - /// Whether the window should be set as fullscreen upon creation. - /// - /// The default is `None`. - pub fullscreen: Option, - - /// The title of the window in the title bar. - /// - /// The default is `"winit window"`. pub title: String, - - /// Whether the window should be maximized upon creation. - /// - /// The default is `false`. + pub fullscreen: Option, pub maximized: bool, - - /// Whether the window should be immediately visible upon creation. - /// - /// The default is `true`. pub visible: bool, - - /// Whether the the window should be transparent. If this is true, writing colors - /// with alpha values different than `1.0` will produce a transparent window. - /// - /// The default is `false`. pub transparent: bool, - - /// Whether the window should have borders and bars. - /// - /// The default is `true`. pub decorations: bool, - - /// Whether the window should always be on top of other windows. - /// - /// The default is `false`. pub always_on_top: bool, - - /// The window icon. - /// - /// The default is `None`. pub window_icon: Option, } @@ -214,7 +158,7 @@ impl Default for WindowAttributes { } impl WindowBuilder { - /// Initializes a new `WindowBuilder` with default values. + /// Initializes a new builder with default values. #[inline] pub fn new() -> Self { Default::default() @@ -222,31 +166,33 @@ impl WindowBuilder { /// Requests the window to be of specific dimensions. /// - /// See [`Window::set_inner_size`] for details. + /// If this is not set, some platform-specific dimensions will be used. /// - /// [`Window::set_inner_size`]: crate::window::Window::set_inner_size + /// See [`Window::set_inner_size`] for details. #[inline] pub fn with_inner_size>(mut self, size: S) -> Self { self.window.inner_size = Some(size.into()); self } - /// Sets a minimum dimension size for the window. + /// Sets the minimum dimensions a window can have. /// - /// See [`Window::set_min_inner_size`] for details. + /// If this is not set, the window will have no minimum dimensions (aside + /// from reserved). /// - /// [`Window::set_min_inner_size`]: crate::window::Window::set_min_inner_size + /// See [`Window::set_min_inner_size`] for details. #[inline] pub fn with_min_inner_size>(mut self, min_size: S) -> Self { self.window.min_inner_size = Some(min_size.into()); self } - /// Sets a maximum dimension size for the window. + /// Sets the maximum dimensions a window can have. /// - /// See [`Window::set_max_inner_size`] for details. + /// If this is not set, the window will have no maximum or will be set to + /// the primary monitor's dimensions by the platform. /// - /// [`Window::set_max_inner_size`]: crate::window::Window::set_max_inner_size + /// See [`Window::set_max_inner_size`] for details. #[inline] pub fn with_max_inner_size>(mut self, max_size: S) -> Self { self.window.max_inner_size = Some(max_size.into()); @@ -255,9 +201,26 @@ impl WindowBuilder { /// Sets a desired initial position for the window. /// - /// See [`WindowAttributes::position`] for details. + /// If this is not set, some platform-specific position will be chosen. /// - /// [`WindowAttributes::position`]: crate::window::WindowAttributes::position + /// See [`Window::set_outer_position`] for details. + /// + /// ## Platform-specific + /// + /// - **macOS:** The top left corner position of the window content, the + /// window's "inner" position. The window title bar will be placed above + /// it. The window will be positioned such that it fits on screen, + /// maintaining set `inner_size` if any. + /// If you need to precisely position the top left corner of the whole + /// window you have to use [`Window::set_outer_position`] after creating + /// the window. + /// - **Windows:** The top left corner position of the window title bar, + /// the window's "outer" position. + /// There may be a small gap between this position and the window due to + /// the specifics of the Window Manager. + /// - **X11:** The top left corner of the window, the window's "outer" + /// position. + /// - **Others:** Ignored. #[inline] pub fn with_position>(mut self, position: P) -> Self { self.window.position = Some(position.into()); @@ -266,53 +229,53 @@ impl WindowBuilder { /// Sets whether the window is resizable or not. /// - /// See [`Window::set_resizable`] for details. + /// The default is `true`. /// - /// [`Window::set_resizable`]: crate::window::Window::set_resizable + /// See [`Window::set_resizable`] for details. #[inline] pub fn with_resizable(mut self, resizable: bool) -> Self { self.window.resizable = resizable; self } - /// Requests a specific title for the window. + /// Sets the initial title of the window in the title bar. /// - /// See [`Window::set_title`] for details. + /// The default is `"winit window"`. /// - /// [`Window::set_title`]: crate::window::Window::set_title + /// See [`Window::set_title`] for details. #[inline] pub fn with_title>(mut self, title: T) -> Self { self.window.title = title.into(); self } - /// Sets the window fullscreen state. + /// Sets whether the window should be put into fullscreen upon creation. /// - /// See [`Window::set_fullscreen`] for details. + /// The default is `None`. /// - /// [`Window::set_fullscreen`]: crate::window::Window::set_fullscreen + /// See [`Window::set_fullscreen`] for details. #[inline] pub fn with_fullscreen(mut self, fullscreen: Option) -> Self { self.window.fullscreen = fullscreen; self } - /// Requests maximized mode. + /// Request that the window is maximized upon creation. /// - /// See [`Window::set_maximized`] for details. + /// The default is `false`. /// - /// [`Window::set_maximized`]: crate::window::Window::set_maximized + /// See [`Window::set_maximized`] for details. #[inline] pub fn with_maximized(mut self, maximized: bool) -> Self { self.window.maximized = maximized; self } - /// Sets whether the window will be initially hidden or visible. + /// Sets whether the window will be initially visible or hidden. /// - /// See [`Window::set_visible`] for details. + /// The default is to show the window. /// - /// [`Window::set_visible`]: crate::window::Window::set_visible + /// See [`Window::set_visible`] for details. #[inline] pub fn with_visible(mut self, visible: bool) -> Self { self.window.visible = visible; @@ -320,17 +283,28 @@ impl WindowBuilder { } /// Sets whether the background of the window should be transparent. + /// + /// If this is `true`, writing colors with alpha values different than + /// `1.0` will produce a transparent window. + /// + /// The default is `false`. #[inline] pub fn with_transparent(mut self, transparent: bool) -> Self { self.window.transparent = transparent; self } + /// Get whether the window will support transparency. + #[inline] + pub fn transparent(&self) -> bool { + self.window.transparent + } + /// Sets whether the window should have a border, a title bar, etc. /// - /// See [`Window::set_decorations`] for details. + /// The default is `true`. /// - /// [`Window::set_decorations`]: crate::window::Window::set_decorations + /// See [`Window::set_decorations`] for details. #[inline] pub fn with_decorations(mut self, decorations: bool) -> Self { self.window.decorations = decorations; @@ -339,9 +313,9 @@ impl WindowBuilder { /// Sets whether or not the window will always be on top of other windows. /// - /// See [`Window::set_always_on_top`] for details. + /// The default is `false`. /// - /// [`Window::set_always_on_top`]: crate::window::Window::set_always_on_top + /// See [`Window::set_always_on_top`] for details. #[inline] pub fn with_always_on_top(mut self, always_on_top: bool) -> Self { self.window.always_on_top = always_on_top; @@ -350,9 +324,9 @@ impl WindowBuilder { /// Sets the window icon. /// - /// See [`Window::set_window_icon`] for details. + /// The default is `None`. /// - /// [`Window::set_window_icon`]: crate::window::Window::set_window_icon + /// See [`Window::set_window_icon`] for details. #[inline] pub fn with_window_icon(mut self, window_icon: Option) -> Self { self.window.window_icon = window_icon; @@ -363,9 +337,10 @@ impl WindowBuilder { /// /// Possible causes of error include denied permission, incompatible system, and lack of memory. /// - /// Platform-specific behavior: - /// - **Web**: The window is created but not inserted into the web page automatically. Please - /// see the web platform module for more information. + /// ## Platform-specific + /// + /// - **Web:** The window is created but not inserted into the web page automatically. Please + /// see the web platform module for more information. #[inline] pub fn build( self, @@ -389,11 +364,12 @@ impl Window { /// Error should be very rare and only occur in case of permission denied, incompatible system, /// out of memory, etc. /// - /// Platform-specific behavior: - /// - **Web**: The window is created but not inserted into the web page automatically. Please - /// see the web platform module for more information. + /// ## Platform-specific + /// + /// - **Web:** The window is created but not inserted into the web page automatically. Please + /// see the web platform module for more information. /// - /// [`WindowBuilder::new().build(event_loop)`]: crate::window::WindowBuilder::build + /// [`WindowBuilder::new().build(event_loop)`]: WindowBuilder::build #[inline] pub fn new(event_loop: &EventLoopWindowTarget) -> Result { let builder = WindowBuilder::new(); @@ -411,7 +387,7 @@ impl Window { /// See the [`dpi`](crate::dpi) module for more information. /// /// Note that this value can change depending on user action (for example if the window is - /// moved to another screen); as such, tracking `WindowEvent::ScaleFactorChanged` events is + /// moved to another screen); as such, tracking [`WindowEvent::ScaleFactorChanged`] events is /// the most robust way to track the DPI you need to use to draw. /// /// ## Platform-specific @@ -421,19 +397,20 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s /// [`contentScaleFactor`]. /// + /// [`WindowEvent::ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc #[inline] pub fn scale_factor(&self) -> f64 { self.window.scale_factor() } - /// Emits a `WindowEvent::RedrawRequested` event in the associated event loop after all OS + /// Emits a [`Event::RedrawRequested`] event in the associated event loop after all OS /// events have been processed by the event loop. /// /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with /// OS-requested redraws (e.g. when a window gets resized). /// - /// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared` + /// This function can cause `RedrawRequested` events to be emitted after [`Event::MainEventsCleared`] /// but before `Event::NewEvents` if called in the following circumstances: /// * While processing `MainEventsCleared`. /// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any @@ -443,6 +420,9 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. /// - **Android:** Subsequent calls after `MainEventsCleared` are not handled. + /// + /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested + /// [`Event::MainEventsCleared`]: crate::event::Event::MainEventsCleared #[inline] pub fn request_redraw(&self) { self.window.request_redraw() @@ -454,14 +434,14 @@ impl Window { /// Returns the position of the top-left hand corner of the window's client area relative to the /// top-left hand corner of the desktop. /// - /// The same conditions that apply to `outer_position` apply to this method. + /// The same conditions that apply to [`Window::outer_position`] apply to this method. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window's [safe area] in the screen space coordinate system. /// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns the - /// same value as `outer_position`._ + /// same value as [`Window::outer_position`]._ /// - **Android / Wayland:** Always returns [`NotSupportedError`]. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc @@ -471,14 +451,14 @@ impl Window { } /// Returns the position of the top-left hand corner of the window relative to the - /// top-left hand corner of the desktop. + /// top-left hand corner of the desktop. /// /// Note that the top-left hand corner of the desktop is not necessarily the same as - /// the screen. If the user uses a desktop with multiple monitors, the top-left hand corner - /// of the desktop is the top-left hand corner of the monitor at the top-left of the desktop. + /// the screen. If the user uses a desktop with multiple monitors, the top-left hand corner + /// of the desktop is the top-left hand corner of the monitor at the top-left of the desktop. /// /// The coordinates can be negative if the top-left hand corner of the window is outside - /// of the visible screen region. + /// of the visible screen region. /// /// ## Platform-specific /// @@ -493,8 +473,8 @@ impl Window { /// Modifies the position of the window. /// - /// See `outer_position` for more information about the coordinates. This automatically un-maximizes the - /// window if it's maximized. + /// See [`Window::outer_position`] for more information about the coordinates. + /// This automatically un-maximizes the window if it's maximized. /// /// ```no_run /// # use winit::dpi::{LogicalPosition, PhysicalPosition}; @@ -538,8 +518,8 @@ impl Window { /// Modifies the inner size of the window. /// - /// See `inner_size` for more information about the values. This automatically un-maximizes the - /// window if it's maximized. + /// See [`Window::inner_size`] for more information about the values. + /// This automatically un-maximizes the window if it's maximized. /// /// ```no_run /// # use winit::dpi::{LogicalSize, PhysicalSize}; @@ -566,14 +546,14 @@ impl Window { /// Returns the physical size of the entire window. /// /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), - /// use `inner_size` instead. + /// use [`Window::inner_size`] instead. /// /// ## Platform-specific /// - /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window in + /// - **iOS:** Can only be called on the main thread. Returns the [`PhysicalSize`] of the window in /// screen space coordinates. /// - **Web:** Returns the size of the canvas element. _Note: this returns the same value as - /// `inner_size`._ + /// [`Window::inner_size`]._ #[inline] pub fn outer_size(&self) -> PhysicalSize { self.window.outer_size() @@ -641,6 +621,7 @@ impl Window { /// Modifies the window's visibility. /// /// If `false`, this will hide the window. If `true`, this will show the window. + /// /// ## Platform-specific /// /// - **Android / Wayland / Web:** Unsupported. @@ -650,9 +631,9 @@ impl Window { self.window.set_visible(visible) } - /// Gets the window's current vibility state. + /// Gets the window's current visibility state. /// - /// If `None` means it couldn't be determined so it is not recommended to use this to drive your rendering backend. + /// `None` means it couldn't be determined, so it is not recommended to use this to drive your rendering backend. /// /// ## Platform-specific /// @@ -665,19 +646,18 @@ impl Window { /// Sets whether the window is resizable or not. /// - /// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that + /// Note that making the window unresizable doesn't exempt you from handling [`WindowEvent::Resized`], as that /// event can still be triggered by DPI scaling, entering fullscreen mode, etc. Also, the - /// window could still be resized by calling `[Window::set_inner_size]`. + /// window could still be resized by calling [`Window::set_inner_size`]. /// /// ## Platform-specific /// /// This only has an effect on desktop platforms. /// - /// Due to a bug in XFCE, this has no effect on Xfwm. - /// - /// ## Platform-specific - /// + /// - **X11:** Due to a bug in XFCE, this has no effect on Xfwm. /// - **iOS / Android / Web:** Unsupported. + /// + /// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized #[inline] pub fn set_resizable(&self, resizable: bool) { self.window.set_resizable(resizable) @@ -729,18 +709,18 @@ impl Window { /// /// ## Platform-specific /// - /// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a + /// - **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 + /// [`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` if /// separate spaces are not preferred. /// - /// The dock and the menu bar are always disabled in fullscreen mode. + /// The dock and the menu bar are disabled in exclusive fullscreen mode. /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request. /// - **Windows:** Screen saver is disabled in fullscreen mode. @@ -767,8 +747,6 @@ impl Window { /// ## Platform-specific /// /// - **iOS / Android / Web:** Unsupported. - /// - /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc #[inline] pub fn set_decorations(&self, decorations: bool) { self.window.set_decorations(decorations) @@ -795,18 +773,20 @@ impl Window { self.window.set_always_on_top(always_on_top) } - /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left + /// Sets the window icon. + /// + /// On Windows and X11, this is typically the small icon in the top-left /// corner of the titlebar. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland / macOS:** Unsupported. /// - /// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's - /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. + /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's + /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. /// - /// 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. + /// - **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. #[inline] pub fn set_window_icon(&self, window_icon: Option) { self.window.set_window_icon(window_icon) @@ -862,7 +842,7 @@ impl Window { /// ## Platform-specific /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined. - /// - ** iOS / Android / Web :** Unsupported. + /// - **iOS / Android / Web:** Unsupported. /// /// [`Ime`]: crate::event::WindowEvent::Ime /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput @@ -889,14 +869,14 @@ impl Window { /// Requests user attention to the window, this has no effect if the application /// is already focused. How requesting for user attention manifests is platform dependent, - /// see `UserAttentionType` for details. + /// see [`UserAttentionType`] for details. /// /// Providing `None` will unset the request for user attention. Unsetting the request for /// user attention might not be done automatically by the WM when the window receives input. /// /// ## Platform-specific /// - /// - **iOS / Android / Web :** Unsupported. + /// - **iOS / Android / Web:** Unsupported. /// - **macOS:** `None` has no effect. /// - **X11:** Requests for user attention must be manually cleared. /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. @@ -941,18 +921,24 @@ impl Window { self.window.set_cursor_position(position.into()) } - /// Grabs the cursor, preventing it from leaving the window. + /// Set grabbing [mode]([`CursorGrabMode`]) on the cursor preventing it from leaving the window. /// - /// There's no guarantee that the cursor will be hidden. You should - /// hide it by yourself if you want so. + /// # Example /// - /// ## Platform-specific + /// First try confining the cursor, and if that fails, try locking it instead. /// - /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward. - /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + /// ```no_run + /// # use winit::event_loop::EventLoop; + /// # use winit::window::{CursorGrabMode, Window}; + /// # let mut event_loop = EventLoop::new(); + /// # let window = Window::new(&event_loop).unwrap(); + /// window.set_cursor_grab(CursorGrabMode::Confined) + /// .or_else(|_e| window.set_cursor_grab(CursorGrabMode::Locked)) + /// .unwrap(); + /// ``` #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - self.window.set_cursor_grab(grab) + pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + self.window.set_cursor_grab(mode) } /// Modifies the cursor's visibility. @@ -1018,11 +1004,13 @@ impl Window { /// Returns the list of all the monitors available on the system. /// - /// This is the same as `EventLoopWindowTarget::available_monitors`, and is provided for convenience. + /// This is the same as [`EventLoopWindowTarget::available_monitors`], and is provided for convenience. /// /// ## Platform-specific /// /// **iOS:** Can only be called on the main thread. + /// + /// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors #[inline] pub fn available_monitors(&self) -> impl Iterator { self.window @@ -1035,30 +1023,82 @@ impl Window { /// /// Returns `None` if it can't identify any monitor as a primary one. /// - /// This is the same as `EventLoopWindowTarget::primary_monitor`, and is provided for convenience. + /// This is the same as [`EventLoopWindowTarget::primary_monitor`], and is provided for convenience. /// /// ## Platform-specific /// /// **iOS:** Can only be called on the main thread. /// **Wayland:** Always returns `None`. + /// + /// [`EventLoopWindowTarget::primary_monitor`]: crate::event_loop::EventLoopWindowTarget::primary_monitor #[inline] pub fn primary_monitor(&self) -> Option { self.window.primary_monitor() } } - -unsafe impl raw_window_handle::HasRawWindowHandle for Window { - /// Returns a `raw_window_handle::RawWindowHandle` for the Window +unsafe impl HasRawWindowHandle for Window { + /// Returns a [`raw_window_handle::RawWindowHandle`] for the Window /// /// ## Platform-specific /// - /// - **Android:** Only available after receiving the Resumed event and before Suspended. *If you* - /// *try to get the handle outside of that period, this function will panic*! - fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + /// ### Android + /// + /// Only available after receiving [`Event::Resumed`] and before [`Event::Suspended`]. *If you + /// try to get the handle outside of that period, this function will panic*! + /// + /// Make sure to release or destroy any resources created from this `RawWindowHandle` (ie. Vulkan + /// or OpenGL surfaces) before returning from [`Event::Suspended`], at which point Android will + /// release the underlying window/surface: any subsequent interaction is undefined behavior. + /// + /// [`Event::Resumed`]: crate::event::Event::Resumed + /// [`Event::Suspended`]: crate::event::Event::Suspended + fn raw_window_handle(&self) -> RawWindowHandle { self.window.raw_window_handle() } } +unsafe impl HasRawDisplayHandle for Window { + /// Returns a [`raw_window_handle::RawDisplayHandle`] used by the [`EventLoop`] that + /// created a window. + /// + /// [`EventLoop`]: crate::event_loop::EventLoop + fn raw_display_handle(&self) -> RawDisplayHandle { + self.window.raw_display_handle() + } +} + +/// The behavior of cursor grabbing. +/// +/// Use this enum with [`Window::set_cursor_grab`] to grab the cursor. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum CursorGrabMode { + /// No grabbing of the cursor is performed. + None, + + /// The cursor is confined to the window area. + /// + /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you + /// want to do so. + /// + /// ## Platform-specific + /// + /// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. + /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. + Confined, + + /// The cursor is locked inside the window area to the certain position. + /// + /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you + /// want to do so. + /// + /// ## Platform-specific + /// + /// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. + /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + Locked, +} + /// Describes the appearance of the mouse cursor. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -1125,7 +1165,7 @@ impl Default for CursorIcon { } /// Fullscreen modes. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Fullscreen { Exclusive(VideoMode), @@ -1133,7 +1173,7 @@ pub enum Fullscreen { Borderless(Option), } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Theme { Light, Dark, @@ -1141,14 +1181,19 @@ pub enum Theme { /// ## Platform-specific /// -/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between `Critical` and `Informational`. -#[derive(Debug, Clone, Copy, PartialEq)] +/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. +/// +/// [`Critical`]: Self::Critical +/// [`Informational`]: Self::Informational +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UserAttentionType { /// ## Platform-specific + /// /// - **macOS:** Bounces the dock icon until the application is in focus. /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. Critical, /// ## Platform-specific + /// /// - **macOS:** Bounces the dock icon once. /// - **Windows:** Flashes the taskbar button until the application is in focus. Informational,