Compare commits

..

1 Commits

Author SHA1 Message Date
John Nunley
19477d3f03 doc: Add installation instructions to docs
This commit adds installation instructions to the top-level
documentation for lib.rs. The main goal is to give the dependencies that
X11/Wayland use in a formal place, since it's not written down anywhere
to my knowledge.

Signed-off-by: John Nunley <dev@notgull.net>
2024-12-08 08:30:31 -08:00
134 changed files with 6615 additions and 6814 deletions

View File

@@ -74,19 +74,15 @@ jobs:
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, } - { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, } - { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude: exclude:
# Web on nightly needs extra arguments # Web on nightly needs extra arguments
- toolchain: nightly - toolchain: nightly
platform: { name: 'Web' } platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
# Rustup is broken.
- toolchain: nightly
platform: { name: 'Windows 32bit GNU' }
# Android is tested on stable-3 # Android is tested on stable-3
- toolchain: '1.73' - toolchain: '1.73'
platform: { name: 'Android' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
# Redox OS doesn't follow MSRV # Redox OS doesn't follow MSRV
- toolchain: '1.73' - toolchain: '1.73'
platform: { name: 'Redox OS' } platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
include: include:
- toolchain: '1.73' - toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
@@ -186,12 +182,6 @@ jobs:
matrix.toolchain != '1.73' matrix.toolchain != '1.73'
run: cargo test -p dpi run: cargo test -p dpi
- name: Check dpi crate (no_std)
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.73'
run: cargo check -p dpi --no-default-features
- name: Build tests - name: Build tests
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&

View File

@@ -19,16 +19,6 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your
life during review it's recommended to check the "give contributors write access life during review it's recommended to check the "give contributors write access
to the branch" checkbox. to the branch" checkbox.
We use unstable Rustfmt options across the project, so please run
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
the maintainers can do it for you before merging, just state so in your pull
request description. For details on how to use nightly, consult [the
documentation][toolchains].
When editing markdown files (`.md`) they must be wrapped at 80 characters.
[toolchains]: https://rust-lang.github.io/rustup/concepts/toolchains.html
#### Handling review #### Handling review
During the review process certain events could require an action from your side, During the review process certain events could require an action from your side,

View File

@@ -21,7 +21,7 @@ name = "winit"
readme = "README.md" readme = "README.md"
repository.workspace = true repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
version = "0.30.10" version = "0.30.5"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ features = [
@@ -78,7 +78,7 @@ cfg_aliases = "0.2.1"
[dependencies] [dependencies]
bitflags = "2" bitflags = "2"
cursor-icon = "1.1.0" cursor-icon = "1.1.0"
dpi = { version = "0.1.2", path = "dpi" } dpi = { version = "0.1.1", path = "dpi" }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] } rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
smol_str = "0.3" smol_str = "0.3"
@@ -104,15 +104,14 @@ ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# AppKit or UIKit # AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.6.1" block2 = "0.5.1"
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] } core-foundation = "0.9.3"
objc2 = "0.6.1" objc2 = "0.5.2"
# AppKit # AppKit
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
objc2-app-kit = { version = "0.3.1", default-features = false, features = [ core-graphics = "0.23.1"
"std", objc2-app-kit = { version = "0.2.2", features = [
"objc2-core-foundation",
"NSAppearance", "NSAppearance",
"NSApplication", "NSApplication",
"NSBitmapImageRep", "NSBitmapImageRep",
@@ -129,7 +128,6 @@ objc2-app-kit = { version = "0.3.1", default-features = false, features = [
"NSMenu", "NSMenu",
"NSMenuItem", "NSMenuItem",
"NSOpenGLView", "NSOpenGLView",
"NSPanel",
"NSPasteboard", "NSPasteboard",
"NSResponder", "NSResponder",
"NSRunningApplication", "NSRunningApplication",
@@ -142,37 +140,9 @@ objc2-app-kit = { version = "0.3.1", default-features = false, features = [
"NSWindowScripting", "NSWindowScripting",
"NSWindowTabGroup", "NSWindowTabGroup",
] } ] }
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [ objc2-foundation = { version = "0.2.2", features = [
"std",
"block2", "block2",
"CFBase", "dispatch",
"CFCGTypes",
"CFData",
"CFRunLoop",
"CFString",
"CFUUID",
] }
objc2-core-graphics = { version = "0.3.1", default-features = false, features = [
"std",
"libc",
"CGDirectDisplay",
"CGDisplayConfiguration",
"CGDisplayFade",
"CGError",
"CGRemoteOperation",
"CGWindowLevel",
] }
objc2-core-video = { version = "0.3.1", default-features = false, features = [
"std",
"objc2-core-graphics",
"CVBase",
"CVReturn",
"CVDisplayLink",
] }
objc2-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray", "NSArray",
"NSAttributedString", "NSAttributedString",
"NSData", "NSData",
@@ -194,29 +164,20 @@ objc2-foundation = { version = "0.3.1", default-features = false, features = [
# UIKit # UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies] [target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [ objc2-foundation = { version = "0.2.2", features = [
"std",
"CFCGTypes",
"CFBase",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2", "block2",
"objc2-core-foundation", "dispatch",
"NSArray", "NSArray",
"NSEnumerator", "NSEnumerator",
"NSGeometry", "NSGeometry",
"NSObjCRuntime", "NSObjCRuntime",
"NSOperation", "NSOperation",
"NSString", "NSString",
"NSProcessInfo",
"NSThread", "NSThread",
"NSSet", "NSSet",
] } ] }
objc2-ui-kit = { version = "0.3.1", default-features = false, features = [ objc2-ui-kit = { version = "0.2.2", features = [
"std",
"objc2-core-foundation",
"UIApplication", "UIApplication",
"UIDevice", "UIDevice",
"UIEvent", "UIEvent",
@@ -242,7 +203,7 @@ objc2-ui-kit = { version = "0.3.1", default-features = false, features = [
# Windows # Windows
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1" unicode-segmentation = "1.7.1"
windows-sys = { version = "0.59.0", features = [ windows-sys = { version = "0.52.0", features = [
"Win32_Devices_HumanInterfaceDevice", "Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation", "Win32_Foundation",
"Win32_Globalization", "Win32_Globalization",
@@ -288,12 +249,12 @@ sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-feature
"calloop", "calloop",
], optional = true } ], optional = true }
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true } sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = [ wayland-backend = { version = "0.3.5", default-features = false, features = [
"client_system", "client_system",
], optional = true } ], optional = true }
wayland-client = { version = "0.31.10", optional = true } wayland-client = { version = "0.31.4", optional = true }
wayland-protocols = { version = "0.32.8", features = ["staging"], optional = true } wayland-protocols = { version = "0.32.2", features = ["staging"], optional = true }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"], optional = true } wayland-protocols-plasma = { version = "0.3.2", features = ["client"], optional = true }
x11-dl = { version = "2.19.1", optional = true } x11-dl = { version = "2.19.1", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = [ x11rb = { version = "0.13.0", default-features = false, features = [
"allow-unsafe-code", "allow-unsafe-code",

View File

@@ -8,7 +8,7 @@
```toml ```toml
[dependencies] [dependencies]
winit = "0.30.10" winit = "0.30.5"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@@ -33,10 +33,6 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
show something on the window you need to use the platform-specific getters provided by winit, or show something on the window you need to use the platform-specific getters provided by winit, or
another library. another library.
## CONTRIBUING
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
## MSRV Policy ## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to
@@ -71,9 +67,3 @@ same MSRV policy.
### Platform-specific usage ### Platform-specific usage
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage. Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage.
### Repository License
Note that the license in `LICENSE` doesn't apply in full to the DPI package [./dpi](./dpi).
Full details can be found in that folder's README.
<!-- This doesn't apply to users of the Winit crate, but this is also the repository level README -->

View File

@@ -11,10 +11,7 @@ Unreleased` header.
## Unreleased ## Unreleased
## 0.1.2
- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types. - Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
- Make `no_std` compatible. If you use this functionality, DPI's license has changed.
## 0.1.1 ## 0.1.1

View File

@@ -3,22 +3,16 @@ categories = ["gui"]
description = "Types for handling UI scaling" description = "Types for handling UI scaling"
edition.workspace = true edition.workspace = true
keywords = ["DPI", "HiDPI", "scale-factor"] keywords = ["DPI", "HiDPI", "scale-factor"]
# N.B. This is "AND", because of the imported libm code. license.workspace = true
license = "Apache-2.0 AND MIT"
name = "dpi" name = "dpi"
repository.workspace = true repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
version = "0.1.2" version = "0.1.1"
[features] [features]
default = ["std"]
mint = ["dep:mint"] mint = ["dep:mint"]
serde = ["dep:serde"] serde = ["dep:serde"]
# Access mathematical functions using the standard library implementations
std = []
[dependencies] [dependencies]
mint = { workspace = true, optional = true } mint = { workspace = true, optional = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }

View File

@@ -1,51 +0,0 @@
rust-lang/libm as a whole is available for use under the MIT license:
------------------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
This Rust library contains the following copyrights:
Copyright (c) 2018 Jorge Aparicio
Portions of this software are derived from third-party works licensed under
terms compatible with the above MIT license:
* musl libc https://www.musl-libc.org/. This library contains the following
copyright:
Copyright © 2005-2020 Rich Felker, et al.
* The CORE-MATH project https://core-math.gitlabpages.inria.fr/. CORE-MATH
routines are available under the MIT license on a per-file basis.
The musl libc COPYRIGHT file also includes the following notice relevant to
math portions of the library:
------------------------------------------------------------------------------
Much of the math library code (src/math/* and src/complex/*) is
Copyright © 1993,2004 Sun Microsystems or
Copyright © 2003-2011 David Schultz or
Copyright © 2003-2009 Steven G. Kargl or
Copyright © 2003-2009 Bruce D. Evans or
Copyright © 2008 Stephen L. Moshier or
Copyright © 2017-2018 Arm Limited
and labelled as such in comments in the individual source files. All
have been licensed under extremely permissive terms.
------------------------------------------------------------------------------

View File

@@ -1,18 +0,0 @@
# DPI
Full docs can be found on docs.rs.
## License
Most of DPI is licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE)).
All files except for `src/libm.rs` (and `LICENSE-LIBM-MIT`) are available solely under that license.
For its `no_std` support, DPI uses code from the [libm](https://crates.io/crates/libm) crate.
This is in the `libm.rs` file, and is licensed solely under the MIT Licence ([LICENSE-LIBM-MIT](LICENSE-LIBM-MIT)).
That file contains details of all potentially applicable copyright notices.
This is feature gated to only be included if you disable the `std` feature, otherwise it will not be compiled into your final binary
(and so these license terms will not apply).
Overall, this means that the license for this crate depends on what features you have enabled.
If you enable the `std` feature, then DPI uses only code available under the Apache-2.0 license, and so can be used under the terms of that license.
However, if you disable the `std` feature, then both these licenses must be followed to use the crate as a whole.

View File

@@ -54,25 +54,13 @@
//! //!
//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). //! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions. //! * `mint`: Enables mint (math interoperability standard types) conversions.
//! * `std` (enabled by default): Uses the standard library mathematical functions (normally through
//! your target platform's libm). This feature also changes the library's license from `Apache-2.0
//! AND MIT` to `APACHE-2.0` (only). For full details, see the package README.
//! //!
//! To use this library on a target without the standard library available, you should disable
//! default features (thus disabling the `std` feature, with the license consequences thereof).
//! //!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![cfg_attr(feature = "std", forbid(unsafe_code))] #![forbid(unsafe_code)]
#![no_std]
#[cfg(not(feature = "std"))]
mod libm;
#[cfg(any(feature = "std", test))]
extern crate std;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -86,32 +74,32 @@ pub trait Pixel: Copy + Into<f64> {
impl Pixel for u8 { impl Pixel for u8 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
round(f) as u8 f.round() as u8
} }
} }
impl Pixel for u16 { impl Pixel for u16 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
round(f) as u16 f.round() as u16
} }
} }
impl Pixel for u32 { impl Pixel for u32 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
round(f) as u32 f.round() as u32
} }
} }
impl Pixel for i8 { impl Pixel for i8 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
round(f) as i8 f.round() as i8
} }
} }
impl Pixel for i16 { impl Pixel for i16 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
round(f) as i16 f.round() as i16
} }
} }
impl Pixel for i32 { impl Pixel for i32 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
round(f) as i32 f.round() as i32
} }
} }
impl Pixel for f32 { impl Pixel for f32 {
@@ -125,15 +113,6 @@ impl Pixel for f64 {
} }
} }
/// Round f to the closest integer, rounding away from `0.0`
#[inline]
fn round(f: f64) -> f64 {
#[cfg(feature = "std")]
return f.round();
#[cfg(not(feature = "std"))]
return libm::round(f);
}
/// Checks that the scale factor is a normal positive `f64`. /// Checks that the scale factor is a normal positive `f64`.
/// ///
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing /// All functions that take a scale factor assert that this will return `true`. If you're sourcing
@@ -1291,20 +1270,20 @@ mod tests {
// Eat coverage for the Debug impls et al // Eat coverage for the Debug impls et al
#[test] #[test]
fn ensure_attrs_do_not_panic() { fn ensure_attrs_do_not_panic() {
let _ = std::format!("{:?}", LogicalPosition::<u32>::default().clone()); let _ = format!("{:?}", LogicalPosition::<u32>::default().clone());
HashSet::new().insert(LogicalPosition::<u32>::default()); HashSet::new().insert(LogicalPosition::<u32>::default());
let _ = std::format!("{:?}", PhysicalPosition::<u32>::default().clone()); let _ = format!("{:?}", PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(PhysicalPosition::<u32>::default()); HashSet::new().insert(PhysicalPosition::<u32>::default());
let _ = std::format!("{:?}", LogicalSize::<u32>::default().clone()); let _ = format!("{:?}", LogicalSize::<u32>::default().clone());
HashSet::new().insert(LogicalSize::<u32>::default()); HashSet::new().insert(LogicalSize::<u32>::default());
let _ = std::format!("{:?}", PhysicalSize::<u32>::default().clone()); let _ = format!("{:?}", PhysicalSize::<u32>::default().clone());
HashSet::new().insert(PhysicalSize::<u32>::default()); HashSet::new().insert(PhysicalSize::<u32>::default());
let _ = std::format!("{:?}", Size::Physical((1, 2).into()).clone()); let _ = format!("{:?}", Size::Physical((1, 2).into()).clone());
let _ = std::format!("{:?}", Position::Physical((1, 2).into()).clone()); let _ = format!("{:?}", Position::Physical((1, 2).into()).clone());
} }
#[test] #[test]

View File

@@ -1,56 +0,0 @@
// Copyright (c) 2018 Jorge Aparicio
// Copyright © 2005-2020 Rich Felker, et al.
// Copyright © 1993,2004 Sun Microsystems or
// Copyright © 2003-2011 David Schultz or
// Copyright © 2003-2009 Steven G. Kargl or
// Copyright © 2003-2009 Bruce D. Evans or
// Copyright © 2008 Stephen L. Moshier or
// Copyright © 2017-2018 Arm Limited
// SPDX-License-Identifier: MIT
// This file is licensed solely under the terms discussed in LICENSE-LIBM-MIT at the crate root.
// See the package-level README for full details.
// Taken from https://github.com/rust-lang/libm/blob/master/src/math/mod.rs#L1
macro_rules! force_eval {
($e:expr) => {
unsafe { ::core::ptr::read_volatile(&$e) }
};
}
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/round.rs
pub(crate) fn round(x: f64) -> f64 {
trunc(x + copysign(0.5 - 0.25 * f64::EPSILON, x))
}
// Adapted from: https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/trunc.rs#L8-L12
#[allow(clippy::needless_late_init /*, reason = "The original libm code uses this style" */)]
fn trunc(x: f64) -> f64 {
let x1p120 = f64::from_bits(0x4770000000000000); // 0x1p120f === 2 ^ 120
let mut i: u64 = x.to_bits();
let mut e: i64 = ((i >> 52) & 0x7ff) as i64 - 0x3ff + 12;
let m: u64;
if e >= 52 + 12 {
return x;
}
if e < 12 {
e = 1;
}
m = -1i64 as u64 >> e;
if (i & m) == 0 {
return x;
}
force_eval!(x + x1p120);
i &= !m;
f64::from_bits(i)
}
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/copysign.rs
fn copysign(x: f64, y: f64) -> f64 {
let mut ux = x.to_bits();
let uy = y.to_bits();
ux &= (!0) >> 1;
ux |= uy & (1 << 63);
f64::from_bits(ux)
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,22 +13,10 @@ fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Debug)] #[derive(Default)]
struct WindowData {
window: Box<dyn Window>,
color: u32,
}
impl WindowData {
fn new(window: Box<dyn Window>, color: u32) -> Self {
Self { window, color }
}
}
#[derive(Default, Debug)]
struct Application { struct Application {
parent_window_id: Option<WindowId>, parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, WindowData>, windows: HashMap<WindowId, Box<dyn Window>>,
} }
impl ApplicationHandler for Application { impl ApplicationHandler for Application {
@@ -38,10 +26,11 @@ fn main() -> Result<(), impl std::error::Error> {
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32)); .with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap(); let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id()); println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id()); self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb)); self.windows.insert(window.id(), window);
} }
fn window_event( fn window_event(
@@ -67,24 +56,15 @@ fn main() -> Result<(), impl std::error::Error> {
event: KeyEvent { state: ElementState::Pressed, .. }, event: KeyEvent { state: ElementState::Pressed, .. },
.. ..
} => { } => {
let child_index = self.windows.len() - 1;
let child_color =
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap(); let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
let child_id = child_window.id(); let child_id = child_window.id();
println!("Child window created with id: {child_id:?}"); println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, WindowData::new(child_window, child_color)); self.windows.insert(child_id, child_window);
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) { if let Some(window) = self.windows.get(&window_id) {
if window_id == self.parent_window_id.unwrap() { fill::fill_window(window.as_ref());
fill::fill_window(window.window.as_ref());
} else {
fill::fill_window_with_color(window.window.as_ref(), window.color);
}
} }
}, },
_ => (), _ => (),
@@ -95,20 +75,12 @@ fn main() -> Result<(), impl std::error::Error> {
fn spawn_child_window( fn spawn_child_window(
parent: &dyn Window, parent: &dyn Window,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
child_count: usize,
) -> Box<dyn Window> { ) -> Box<dyn Window> {
let parent = parent.raw_window_handle().unwrap(); let parent = parent.raw_window_handle().unwrap();
// As child count increases, x goes from 0*128 to 5*128 and then repeats
let x: f64 = child_count.rem_euclid(5) as f64 * 128.0;
// After 5 windows have been put side by side horizontally, a new row starts
let y: f64 = (child_count / 5) as f64 * 96.0;
let mut window_attributes = WindowAttributes::default() let mut window_attributes = WindowAttributes::default()
.with_title("child window") .with_title("child window")
.with_surface_size(LogicalSize::new(128.0f32, 96.0)) .with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(x, y))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true); .with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window. // `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) }; window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };

View File

@@ -46,7 +46,7 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.run_app(ControlFlowDemo::default()) event_loop.run_app(ControlFlowDemo::default())
} }
#[derive(Default, Debug)] #[derive(Default)]
struct ControlFlowDemo { struct ControlFlowDemo {
mode: Mode, mode: Mode,
request_redraw: bool, request_redraw: bool,

View File

@@ -1,65 +0,0 @@
use std::error::Error;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[path = "util/tracing.rs"]
mod tracing;
fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let event_loop = EventLoop::new()?;
let app = Application::new();
Ok(event_loop.run_app(app)?)
}
/// Application state and event handling.
#[derive(Debug)]
struct Application {
window: Option<Box<dyn Window>>,
}
impl Application {
fn new() -> Self {
Self { window: None }
}
}
impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes =
WindowAttributes::default().with_title("Drag and drop files on me!");
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::DragLeft { .. }
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. } => {
println!("{:?}", event);
},
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
WindowEvent::CloseRequested => {
event_loop.exit();
},
_ => {},
}
}
}

View File

@@ -16,7 +16,7 @@ fn main() -> std::process::ExitCode {
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Default, Debug)] #[derive(Default)]
struct PumpDemo { struct PumpDemo {
window: Option<Box<dyn Window>>, window: Option<Box<dyn Window>>,
} }

View File

@@ -14,7 +14,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Default, Debug)] #[derive(Default)]
struct App { struct App {
idx: usize, idx: usize,
window_id: Option<WindowId>, window_id: Option<WindowId>,

View File

@@ -9,12 +9,7 @@
#[allow(unused_imports)] #[allow(unused_imports)]
pub use platform::cleanup_window; pub use platform::cleanup_window;
#[allow(unused_imports)]
pub use platform::fill_window; pub use platform::fill_window;
#[allow(unused_imports)]
pub use platform::fill_window_with_animated_color;
#[allow(unused_imports)]
pub use platform::fill_window_with_color;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
mod platform { mod platform {
@@ -75,7 +70,7 @@ mod platform {
} }
} }
pub fn fill_window_with_color(window: &dyn Window, color: u32) { pub fn fill_window(window: &dyn Window) {
GC.with(|gc| { GC.with(|gc| {
let size = window.surface_size(); let size = window.surface_size();
let (Some(width), Some(height)) = let (Some(width), Some(height)) =
@@ -89,31 +84,17 @@ mod platform {
let surface = let surface =
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window); gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color // Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xff181818;
surface.resize(width, height).expect("Failed to resize the softbuffer surface"); surface.resize(width, height).expect("Failed to resize the softbuffer surface");
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer"); let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(color); buffer.fill(DARK_GRAY);
buffer.present().expect("Failed to present the softbuffer buffer"); buffer.present().expect("Failed to present the softbuffer buffer");
}) })
} }
#[allow(dead_code)]
pub fn fill_window(window: &dyn Window) {
fill_window_with_color(window, 0xff181818);
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(window: &dyn Window, start: std::time::Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
fill_window_with_color(window, color);
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) { pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| { GC.with(|gc| {
@@ -127,24 +108,10 @@ mod platform {
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
mod platform { mod platform {
#[allow(dead_code)]
pub fn fill_window(_window: &dyn winit::window::Window) { pub fn fill_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms. // No-op on mobile platforms.
} }
#[allow(dead_code)]
pub fn fill_window_with_color(_window: &dyn winit::window::Window, _color: u32) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(
_window: &dyn winit::window::Window,
_start: std::time::Instant,
) {
// No-op on mobile platforms.
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn cleanup_window(_window: &dyn winit::window::Window) { pub fn cleanup_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms. // No-op on mobile platforms.

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,6 @@ fn main() -> Result<(), Box<dyn Error>> {
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Debug)]
pub struct XEmbedDemo { pub struct XEmbedDemo {
parent_window_id: u32, parent_window_id: u32,
window: Option<Box<dyn Window>>, window: Option<Box<dyn Window>>,

View File

@@ -2,21 +2,11 @@
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop; use crate::event_loop::ActiveEventLoop;
#[cfg(macos_platform)] #[cfg(any(docsrs, macos_platform))]
use crate::platform::macos::ApplicationHandlerExtMacOS; use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId; use crate::window::WindowId;
/// The handler of application-level events. /// The handler of the application events.
///
/// See [the top-level docs] for example usage, and [`EventLoop::run_app`] for an overview of when
/// events are delivered.
///
/// This is [dropped] when the event loop is shut down. Note that this only works if you're passing
/// the entire state to [`EventLoop::run_app`] (passing `&mut app` won't work).
///
/// [the top-level docs]: crate
/// [`EventLoop::run_app`]: crate::event_loop::EventLoop::run_app
/// [dropped]: std::ops::Drop
pub trait ApplicationHandler { pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed. /// Emitted when new events arrive from the OS to be processed.
/// ///
@@ -67,6 +57,7 @@ pub trait ApplicationHandler {
/// ///
/// [`resumed()`]: Self::resumed() /// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended() /// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) { fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop; let _ = event_loop;
} }
@@ -171,6 +162,8 @@ pub trait ApplicationHandler {
/// ///
/// let (sender, receiver) = mpsc::channel(); /// let (sender, receiver) = mpsc::channel();
/// ///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop /// // Send an event in a loop
/// let proxy = event_loop.create_proxy(); /// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || { /// let background_thread = thread::spawn(move || {
@@ -178,7 +171,7 @@ pub trait ApplicationHandler {
/// loop { /// loop {
/// println!("sending: {i}"); /// println!("sending: {i}");
/// if sender.send(i).is_err() { /// if sender.send(i).is_err() {
/// // Stop sending once the receiver is dropped /// // Stop sending once `MyApp` is dropped
/// break; /// break;
/// } /// }
/// // Trigger the wake-up _after_ we placed the event in the channel. /// // Trigger the wake-up _after_ we placed the event in the channel.
@@ -189,8 +182,9 @@ pub trait ApplicationHandler {
/// } /// }
/// }); /// });
/// ///
/// event_loop.run_app(MyApp { receiver })?; /// event_loop.run_app(&mut app)?;
/// ///
/// drop(app);
/// background_thread.join().unwrap(); /// background_thread.join().unwrap();
/// ///
/// Ok(()) /// Ok(())
@@ -209,10 +203,6 @@ pub trait ApplicationHandler {
); );
/// Emitted when the OS sends an event to a device. /// Emitted when the OS sends an event to a device.
///
/// For this to be called, it must be enabled with [`EventLoop::listen_device_events`].
///
/// [`EventLoop::listen_device_events`]: crate::event_loop::EventLoop::listen_device_events
fn device_event( fn device_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
@@ -268,7 +258,7 @@ pub trait ApplicationHandler {
/// to the user. This is a good place to stop refreshing UI, running animations and other visual /// to the user. This is a good place to stop refreshing UI, running animations and other visual
/// things. It is driven by Android's [`onStop()`] method. /// things. It is driven by Android's [`onStop()`] method.
/// ///
/// After this event the application either receives [`resumed()`] again, or will exit. /// After this event the application either receives [`resumed()`] again, or [`exiting()`].
/// ///
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop() /// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
/// ///
@@ -278,6 +268,7 @@ pub trait ApplicationHandler {
/// ///
/// [`resumed()`]: Self::resumed() /// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended() /// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) { fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop; let _ = event_loop;
} }
@@ -319,6 +310,14 @@ pub trait ApplicationHandler {
let _ = event_loop; let _ = event_loop;
} }
/// Emitted when the event loop is being shut down.
///
/// This is irreversible - if this method is called, it is guaranteed that the event loop
/// will exit right after.
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application has received a memory warning. /// Emitted when the application has received a memory warning.
/// ///
/// ## Platform-specific /// ## Platform-specific
@@ -350,7 +349,7 @@ pub trait ApplicationHandler {
/// The macOS-specific handler. /// The macOS-specific handler.
/// ///
/// The return value from this should not change at runtime. /// The return value from this should not change at runtime.
#[cfg(macos_platform)] #[cfg(any(docsrs, macos_platform))]
#[inline(always)] #[inline(always)]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
None None
@@ -414,12 +413,17 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
(**self).destroy_surfaces(event_loop); (**self).destroy_surfaces(event_loop);
} }
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline] #[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) { fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);
} }
#[cfg(macos_platform)] #[cfg(any(docsrs, macos_platform))]
#[inline] #[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler() (**self).macos_handler()
@@ -483,12 +487,17 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
(**self).destroy_surfaces(event_loop); (**self).destroy_surfaces(event_loop);
} }
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline] #[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) { fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop); (**self).memory_warning(event_loop);
} }
#[cfg(macos_platform)] #[cfg(any(docsrs, macos_platform))]
#[inline] #[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler() (**self).macos_handler()

View File

@@ -59,29 +59,28 @@ changelog entry.
- Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd` - Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`
and `Serialize` on many types. and `Serialize` on many types.
- Add `MonitorHandle::current_video_mode()`. - Add `MonitorHandle::current_video_mode()`.
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit. - Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit.
- Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS. - Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS.
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
- On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar` - On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar`
to use a larger style of titlebar. to use a larger style of titlebar.
- Add `WindowId::into_raw()` and `from_raw()`. - Add `WindowId::into_raw()` and `from_raw()`.
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all - Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all
pointer events as part of the pointer event overhaul. pointer events as part of the pointer event overhaul.
- Add `DeviceId::into_raw()` and `from_raw()`. - Add `DeviceId::into_raw()` and `from_raw()`.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- Added `Window::surface_position`, which is the position of the surface inside the window. - Added `Window::surface_position`, which is the position of the surface inside the window.
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed. - Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes. - On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`.
- Implement `MonitorHandleProvider` for `MonitorHandle` to access common monitor API.
- On X11, set an "area" attribute on XIM input connection to convey the cursor area.
- Implement `CustomCursorProvider` for `CustomCursor` to access cursor API.
- Add `CustomCursorSource::Url`, `CustomCursorSource::from_animation`.
- Implement `CustomIconProvider` for `RgbaIcon`.
- Add `icon` module that exposes winit's icon API.
### Changed ### Changed
- Change `ActiveEventLoop` and `Window` to be traits, and added `cast_ref`/`cast_mut`/`cast` - Change `ActiveEventLoop` to be a trait.
methods to extract the backend type from those. - Change `Window` to be a trait.
- `ActiveEventLoop::create_window` now returns `Box<dyn Window>`. - `ActiveEventLoop::create_window` now returns `Box<dyn Window>`.
- `ApplicationHandler` now uses `dyn ActiveEventLoop`. - `ApplicationHandler` now uses `dyn ActiveEventLoop`.
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`. - On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
@@ -114,8 +113,10 @@ changelog entry.
- Changed how `ModifiersState` is serialized by Serde. - Changed how `ModifiersState` is serialized by Serde.
- `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`. - `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`.
- `MonitorHandle::position()` now returns an `Option`. - `MonitorHandle::position()` now returns an `Option`.
- On macOS, remove custom application delegates. You are now allowed to override the - On iOS and macOS, remove custom application delegates. You are now allowed to override the
application delegate yourself. application delegate yourself.
- On iOS, no longer act as-if the application successfully open all URLs. Override
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
- On X11, remove our dependency on libXcursor. (#3749) - On X11, remove our dependency on libXcursor. (#3749)
- Renamed the following APIs to make it clearer that the sizes apply to the underlying surface: - Renamed the following APIs to make it clearer that the sizes apply to the underlying surface:
- `WindowEvent::Resized` to `SurfaceResized`. - `WindowEvent::Resized` to `SurfaceResized`.
@@ -164,37 +165,6 @@ changelog entry.
- On macOS, no longer emit `Focused` upon window creation. - On macOS, no longer emit `Focused` upon window creation.
- On iOS, emit more events immediately, instead of queuing them. - On iOS, emit more events immediately, instead of queuing them.
- Update `smol_str` to version `0.3` - Update `smol_str` to version `0.3`
- Rename `VideoModeHandle` to `VideoMode`, now it only stores plain data.
- Make `Fullscreen::Exclusive` contain `(MonitorHandle, VideoMode)`.
- Reworked the file drag-and-drop API.
The `WindowEvent::DroppedFile`, `WindowEvent::HoveredFile` and `WindowEvent::HoveredFileCancelled`
events have been removed, and replaced with `WindowEvent::DragEntered`, `WindowEvent::DragMoved`,
`WindowEvent::DragDropped` and `WindowEvent::DragLeft`.
The old drag-and-drop events were emitted once per file. This occurred when files were *first*
hovered over the window, dropped, or left the window. The new drag-and-drop events are emitted
once per set of files dragged, and include a list of all dragged files. They also include the
pointer position.
The rough correspondence is:
- `WindowEvent::HoveredFile` -> `WindowEvent::DragEntered`
- `WindowEvent::DroppedFile` -> `WindowEvent::DragDropped`
- `WindowEvent::HoveredFileCancelled` -> `WindowEvent::DragLeft`
The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves
whilst files are being dragged over the window. It doesn't contain any file paths, just the
pointer position.
- Updated `objc2` to `v0.6`.
- Updated `windows-sys` to `v0.59`.
- To match the corresponding changes in `windows-sys`, the `HWND`, `HMONITOR`, and `HMENU` types
now alias to `*mut c_void` instead of `isize`.
- Removed `KeyEventExtModifierSupplement`, and made the fields `text_with_all_modifiers` and
`key_without_modifiers` public on `KeyEvent` instead.
- Move `window::Fullscreen` to `monitor::Fullscreen`.
- Renamed "super" key to "meta", to match the naming in the W3C specification.
`NamedKey::Super` still exists, but it's non-functional and deprecated, `NamedKey::Meta` should be used instead.
- Move `IconExtWindows` into `WinIcon`.
### Removed ### Removed
@@ -226,18 +196,23 @@ changelog entry.
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and - Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
`ButtonSource` as part of the new pointer event overhaul. `ButtonSource` as part of the new pointer event overhaul.
- Remove `Force::altitude_angle`. - Remove `Force::altitude_angle`.
- Remove `Window::inner_position`, use the new `Window::surface_position` instead. - Removed `Window::inner_position`, use the new `Window::surface_position` instead.
- Remove `CustomCursorExtWeb`, use the `CustomCursorSource`.
- Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead.
- Remove `ApplicationHandler::exited`, the event loop being shut down can now be listened to in
the `Drop` impl on the application handler.
- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead.
- Remove `PartialEq` impl for `WindowAttributes`.
### Fixed ### Fixed
- On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name. - On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name.
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
default activation policy, unless explicitly provided during initialization.
- On macOS, fix crash when calling `drag_window()` without a left click present.
- On X11, key events forward to IME anyway, even when it's disabled.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface. - On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
- On macOS, fixed the scancode conversion for audio volume keys. - On macOS, fixed the scancode conversion for audio volume keys.
- On macOS, fixed the scancode conversion for `IntlBackslash`. - On macOS, fixed the scancode conversion for `IntlBackslash`.
- On macOS, fixed redundant `SurfaceResized` event at window creation. - On macOS, fixed redundant `SurfaceResized` event at window creation.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.

View File

@@ -1,89 +1,3 @@
## 0.30.10
### Added
- On Windows, add `IconExtWindows::from_resource_name`.
- On Windows, add `CursorGrabMode::Locked`.
- On Wayland, add `WindowExtWayland::xdg_toplevel`.
### Changed
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
- On iOS, remove custom application delegates. You are now allowed to override the
application delegate yourself.
- On iOS, no longer act as-if the application successfully open all URLs. Override
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
### Fixed
- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw.
- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game`
- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode
- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus.
- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`.
- On Wayland, apply fractional scaling to custom cursors.
- On macOS, fixed `run_app_on_demand` returning without closing open windows.
- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates.
- On macOS, store monitor handle to avoid panics after going in/out of sleep.
- On macOS, allow certain invalid monitor handles and return `None` instead of panicking.
- On Windows, fixed `Ime::Preedit` cursor offset calculation.
## 0.30.9
### Changed
- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`.
### Fixed
- On X11, fix crash with uim.
- On X11, fix modifiers for keys that were sent by the same X11 request.
- On iOS, fix high CPU usage even when using `ControlFlow::Wait`.
## 0.30.8
### Added
- `ActivationToken::from_raw` and `ActivationToken::into_raw`.
- On X11, add a workaround for disabling IME on GNOME.
### Fixed
- On Windows, fixed the event loop not waking on accessibility requests.
- On X11, fixed cursor grab mode state tracking on error.
## 0.30.7
### Fixed
- On X11, fixed KeyboardInput delivered twice when IME enabled.
## 0.30.6
### Added
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
### Fixed
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
default activation policy, unless explicitly provided during initialization.
- On macOS, fix crash when calling `drag_window()` without a left click present.
- On X11, key events forward to IME anyway, even when it's disabled.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
## 0.30.5 ## 0.30.5
### Added ### Added

View File

@@ -1,15 +1,13 @@
use core::fmt; use core::fmt;
use std::error::Error; use std::error::Error;
use std::hash::Hash; use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
use crate::utils::{impl_dyn_casting, AsAny}; use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`]. /// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048; pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4; const PIXEL_SIZE: usize = 4;
@@ -53,20 +51,19 @@ impl From<CustomCursor> for Cursor {
/// # use winit::event_loop::ActiveEventLoop; /// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window; /// # use winit::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) { /// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit::window::CustomCursorSource; /// use winit::window::CustomCursor;
/// ///
/// let w = 10; /// let w = 10;
/// let h = 10; /// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize]; /// let rgba = vec![255; (w * h * 4) as usize];
/// ///
/// #[cfg(not(target_family = "wasm"))] /// #[cfg(not(target_family = "wasm"))]
/// let source = CustomCursorSource::from_rgba(rgba, w, h, w / 2, h / 2).unwrap(); /// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
/// ///
/// #[cfg(target_family = "wasm")] /// #[cfg(target_family = "wasm")]
/// let source = CustomCursorSource::Url { /// let source = {
/// url: String::from("http://localhost:3000/cursor.png"), /// use winit::platform::web::CustomCursorExtWeb;
/// hotspot_x: 0, /// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// hotspot_y: 0,
/// }; /// };
/// ///
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) { /// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
@@ -74,96 +71,50 @@ impl From<CustomCursor> for Cursor {
/// } /// }
/// # } /// # }
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor(pub(crate) Arc<dyn CustomCursorProvider>); pub struct CustomCursor {
/// Platforms should make sure this is cheap to clone.
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync { pub(crate) inner: PlatformCustomCursor,
/// Whether a cursor was backed by animation.
fn is_animated(&self) -> bool;
} }
impl PartialEq for CustomCursor { impl CustomCursor {
fn eq(&self, other: &Self) -> bool { /// Creates a new cursor from an rgba buffer.
Arc::ptr_eq(&self.0, &other.0) ///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: impl Into<Vec<u8>>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> {
let _span =
tracing::debug_span!("winit::Cursor::from_rgba", width, height, hotspot_x, hotspot_y)
.entered();
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::from_rgba(
rgba.into(),
width,
height,
hotspot_x,
hotspot_y,
)?,
})
} }
} }
impl Eq for CustomCursor {}
impl Hash for CustomCursor {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl Deref for CustomCursor {
type Target = dyn CustomCursorProvider;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl_dyn_casting!(CustomCursorProvider);
/// Source for [`CustomCursor`]. /// Source for [`CustomCursor`].
/// ///
/// See [`CustomCursor`] for more details. /// See [`CustomCursor`] for more details.
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum CustomCursorSource { pub struct CustomCursorSource {
/// Cursor that is backed by RGBA image. // Some platforms don't support custom cursors.
/// #[allow(dead_code)]
/// See [CustomCursorSource::from_rgba] for more. pub(crate) inner: PlatformCustomCursorSource,
///
/// ## Platform-specific
///
/// - **iOS / Android / Orbital:** Unsupported
Image(CursorImage),
/// Animated cursor.
///
/// See [CustomCursorSource::from_animation] for more.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Animation(CursorAnimation),
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Url { hotspot_x: u16, hotspot_y: u16, url: String },
} }
impl CustomCursorSource { /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image)
}
/// Crates a new animated cursor from multiple [`CustomCursor`]s
/// Supplied `cursors` can't be empty or other animations.
pub fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<Self, BadAnimation> {
CursorAnimation::new(duration, cursors).map(Self::Animation)
}
}
/// An error produced when using [`CustomCursorSource::from_rgba`] with invalid arguments.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadImage { pub enum BadImage {
@@ -213,30 +164,47 @@ impl fmt::Display for BadImage {
impl Error for BadImage {} impl Error for BadImage {}
/// An error produced when using [`CustomCursorSource::from_animation`] with invalid arguments. /// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] /// images.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[allow(dead_code)]
pub enum BadAnimation { #[derive(Debug, Clone, Eq, Hash, PartialEq)]
/// Produced when no cursors were supplied. pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
impl fmt::Display for BadAnimation { #[allow(dead_code)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { impl OnlyCursorImageSource {
match self { pub(crate) fn from_rgba(
Self::Empty => write!(f, "No cursors supplied"), rgba: Vec<u8>,
Self::Animation => write!(f, "A supplied cursor is an animation"), width: u16,
} height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
} }
} }
impl Error for BadAnimation {} /// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
impl Hash for OnlyCursorImage {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl PartialEq for OnlyCursorImage {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for OnlyCursorImage {}
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Clone, Eq, Hash, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct CursorImage { pub(crate) struct CursorImage {
pub(crate) rgba: Vec<u8>, pub(crate) rgba: Vec<u8>,
pub(crate) width: u16, pub(crate) width: u16,
pub(crate) height: u16, pub(crate) height: u16,
@@ -279,22 +247,20 @@ impl CursorImage {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] // Platforms that don't support cursors will export this as `PlatformCustomCursor`.
pub struct CursorAnimation { #[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) duration: Duration, pub(crate) struct NoCustomCursor;
pub(crate) cursors: Vec<CustomCursor>,
}
impl CursorAnimation { #[allow(dead_code)]
pub fn new(duration: Duration, cursors: Vec<CustomCursor>) -> Result<Self, BadAnimation> { impl NoCustomCursor {
if cursors.is_empty() { pub(crate) fn from_rgba(
return Err(BadAnimation::Empty); rgba: Vec<u8>,
} width: u16,
height: u16,
if cursors.iter().any(|cursor| cursor.is_animated()) { hotspot_x: u16,
return Err(BadAnimation::Animation); hotspot_y: u16,
} ) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self { duration, cursors }) Ok(Self)
} }
} }

View File

@@ -1,4 +1,39 @@
//! The event enums and assorted supporting types. //! The event enums and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], 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_app(...)`] like this:
//!
//! ```rust,ignore
//! let mut start_cause = StartCause::Init;
//!
//! while !elwt.exiting() {
//! app.new_events(event_loop, start_cause);
//!
//! for event in (window events, user events, device events) {
//! // This will pick the right method on the application based on the event.
//! app.handle_event(event_loop, event);
//! }
//!
//! for window_id in (redraw windows) {
//! app.window_event(event_loop, window_id, RedrawRequested);
//! }
//!
//! app.about_to_wait(event_loop);
//! start_cause = wait_if_necessary();
//! }
//!
//! app.exiting(event_loop);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order.
//!
//! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, Weak}; use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
@@ -14,9 +49,70 @@ use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::RequestError; use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial; use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}; use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
#[cfg(doc)] #[cfg(doc)]
use crate::window::Window; use crate::window::Window;
use crate::window::{ActivationToken, Theme}; use crate::window::{ActivationToken, Theme, WindowId};
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
// wrap `Event` in some other structure.
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Event {
/// See [`ApplicationHandler::new_events()`] for details.
///
/// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events()
NewEvents(StartCause),
/// See [`ApplicationHandler::window_event()`] for details.
///
/// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event()
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event()`] for details.
///
/// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event()
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
/// See [`ApplicationHandler::suspended()`] for details.
///
/// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended()
Suspended,
/// See [`ApplicationHandler::can_create_surfaces()`] for details.
///
/// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces()
CreateSurfaces,
/// See [`ApplicationHandler::resumed()`] for details.
///
/// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed()
Resumed,
/// See [`ApplicationHandler::about_to_wait()`] for details.
///
/// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait()
AboutToWait,
/// See [`ApplicationHandler::exiting()`] for details.
///
/// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting()
LoopExiting,
/// See [`ApplicationHandler::memory_warning()`] for details.
///
/// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning()
MemoryWarning,
/// User requested a wake up.
UserWakeUp,
}
/// Describes the reason the event loop is resuming. /// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -79,42 +175,28 @@ pub enum WindowEvent {
/// The window has been destroyed. /// The window has been destroyed.
Destroyed, Destroyed,
/// A file drag operation has entered the window. /// A file is being hovered over the window.
DragEntered { ///
/// List of paths that are being dragged onto the window. /// When the user hovers multiple files at once, this event will be emitted for each file
paths: Vec<PathBuf>, /// separately.
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be HoveredFile(PathBuf),
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc). /// A file has been dropped into the window.
position: PhysicalPosition<f64>, ///
}, /// When the user drops multiple files at once, this event will be emitted for each file
/// A file drag operation has moved over the window. /// separately.
DragMoved { ///
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be /// The support for this is known to be incomplete, see [#720] for more
/// negative on some platforms if something is dragged over a window's decorations (title /// information.
/// bar, frame, etc). ///
position: PhysicalPosition<f64>, /// [#720]: https://github.com/rust-windowing/winit/issues/720
}, DroppedFile(PathBuf),
/// The file drag operation has dropped file(s) on the window.
DragDropped { /// A file was hovered, but has exited the window.
/// List of paths that are being dragged onto the window. ///
paths: Vec<PathBuf>, /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be /// hovered.
/// negative on some platforms if something is dragged over a window's decorations (title HoveredFileCancelled,
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// The file drag operation has been cancelled or left the window.
DragLeft {
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
///
/// ## Platform-specific
///
/// - **Windows:** Always emits [`None`].
position: Option<PhysicalPosition<f64>>,
},
/// The window gained or lost focus. /// The window gained or lost focus.
/// ///
@@ -487,7 +569,7 @@ pub enum PointerSource {
/// - **MacOS / Orbital / Wayland / X11:** Always emits [`None`]. /// - **MacOS / Orbital / Wayland / X11:** Always emits [`None`].
/// - **Android:** Will never be [`None`]. If the device doesn't support pressure /// - **Android:** Will never be [`None`]. If the device doesn't support pressure
/// sensitivity, force will either be 0.0 or 1.0. Also see the /// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE). /// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).#[derive(Debug, Clone, Copy, PartialEq)]
/// - **Web:** Will never be [`None`]. If the device doesn't support pressure sensitivity, /// - **Web:** Will never be [`None`]. If the device doesn't support pressure sensitivity,
/// force will be 0.5 when a button is pressed or 0.0 otherwise. /// force will be 0.5 when a button is pressed or 0.0 otherwise.
force: Option<Force>, force: Option<Force>,
@@ -606,12 +688,10 @@ impl FingerId {
/// ///
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera /// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
/// or first-person game controls. Many physical actions, such as mouse movement, can produce both /// or first-person game controls. Many physical actions, such as mouse movement, can produce both
/// device and [window events]. Because window events typically arise from virtual devices /// device and window events. Because window events typically arise from virtual devices
/// (corresponding to GUI pointers and keyboard focus) the device IDs may not match. /// (corresponding to GUI pointers and keyboard focus) the device IDs may not match.
/// ///
/// Note that these events are delivered regardless of input focus. /// Note that these events are delivered regardless of input focus.
///
/// [window events]: WindowEvent
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum DeviceEvent { pub enum DeviceEvent {
/// Change in physical position of a pointing device. /// Change in physical position of a pointing device.
@@ -624,11 +704,11 @@ pub enum DeviceEvent {
/// **Web:** Only returns raw data, not OS accelerated, if [`CursorGrabMode::Locked`] is used /// **Web:** Only returns raw data, not OS accelerated, if [`CursorGrabMode::Locked`] is used
/// and browser support is available, see /// and browser support is available, see
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = "[`ActiveEventLoopExtWeb::is_cursor_lock_raw()`][crate::platform::web::ActiveEventLoopExtWeb::is_cursor_lock_raw()]." doc = "[`ActiveEventLoopExtWeb::is_cursor_lock_raw()`][crate::platform::web::ActiveEventLoopExtWeb::is_cursor_lock_raw()]."
)] )]
#[cfg_attr( #[cfg_attr(
not(web_platform), not(any(web_platform, docsrs)),
doc = "`ActiveEventLoopExtWeb::is_cursor_lock_raw()`." doc = "`ActiveEventLoopExtWeb::is_cursor_lock_raw()`."
)] )]
/// ///
@@ -698,6 +778,12 @@ pub struct KeyEvent {
/// you somehow see this in the wild, we'd like to know :) /// you somehow see this in the wild, we'd like to know :)
pub physical_key: keyboard::PhysicalKey, pub physical_key: keyboard::PhysicalKey,
// Allowing `broken_intra_doc_links` for `logical_key`, because
// `key_without_modifiers` is not available on all platforms
#[cfg_attr(
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links)
)]
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>. /// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
/// ///
/// This has two use cases: /// This has two use cases:
@@ -713,7 +799,7 @@ pub struct KeyEvent {
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the /// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
/// browser/OS. /// browser/OS.
/// ///
/// [`key_without_modifiers`]: Self::key_without_modifiers /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
pub logical_key: keyboard::Key, pub logical_key: keyboard::Key,
/// Contains the text produced by this keypress. /// Contains the text produced by this keypress.
@@ -734,7 +820,7 @@ pub struct KeyEvent {
/// This is `None` if the current keypress cannot /// This is `None` if the current keypress cannot
/// be interpreted as text. /// be interpreted as text.
/// ///
/// See also [`text_with_all_modifiers`][Self::text_with_all_modifiers]. /// See also: `text_with_all_modifiers()`
pub text: Option<SmolStr>, pub text: Option<SmolStr>,
/// Contains the location of this key on the keyboard. /// Contains the location of this key on the keyboard.
@@ -790,33 +876,13 @@ pub struct KeyEvent {
/// ``` /// ```
pub repeat: bool, pub repeat: bool,
/// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd>. /// Platform-specific key event information.
/// ///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`. /// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
/// all modifiers applied.
/// ///
/// ## Platform-specific /// On Android, iOS, Redox and Web, this type is a no-op.
/// pub(crate) platform_specific: platform_impl::KeyEventExtra,
/// - **Android:** Unimplemented, this field is always the same value as `text`.
/// - **iOS:** Unimplemented, this field is always the same value as `text`.
/// - **Web:** Unsupported, this field is always the same value as `text`.
pub text_with_all_modifiers: Option<SmolStr>,
/// This value ignores all modifiers including, but not limited to <kbd>Shift</kbd>,
/// <kbd>Caps Lock</kbd>, and <kbd>Ctrl</kbd>. In most cases this means that the
/// unicode character in the resulting string is lowercase.
///
/// This is useful for key-bindings / shortcut key combinations.
///
/// In case [`logical_key`][Self::logical_key] reports [`Dead`][keyboard::Key::Dead],
/// this will still report the key as `Character` according to the current keyboard
/// layout. This value cannot be `Dead`.
///
/// ## Platform-specific
///
/// - **Android:** Unimplemented, this field is always the same value as `logical_key`.
/// - **iOS:** Unimplemented, this field is always the same value as `logical_key`.
/// - **Web:** Unsupported, this field is always the same value as `logical_key`.
pub key_without_modifiers: keyboard::Key,
} }
/// Describes keyboard modifiers event. /// Describes keyboard modifiers event.
@@ -869,12 +935,12 @@ impl Modifiers {
/// The state of the left super key. /// The state of the left super key.
pub fn lsuper_state(&self) -> ModifiersKeyState { pub fn lsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LMETA) self.mod_state(ModifiersKeys::LSUPER)
} }
/// The state of the right super key. /// The state of the right super key.
pub fn rsuper_state(&self) -> ModifiersKeyState { pub fn rsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RMETA) self.mod_state(ModifiersKeys::RSUPER)
} }
fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState { fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState {
@@ -1127,108 +1193,123 @@ mod tests {
macro_rules! foreach_event { macro_rules! foreach_event {
($closure:expr) => {{ ($closure:expr) => {{
foreach_event!(window: $closure);
foreach_event!(device: $closure);
}};
(window: $closure:expr) => {{
#[allow(unused_mut)] #[allow(unused_mut)]
let mut with_window_event: &mut dyn FnMut(event::WindowEvent) = &mut $closure; let mut x = $closure;
let fid = event::FingerId::from_raw(0); let fid = event::FingerId::from_raw(0);
use crate::event::Ime::Enabled; #[allow(deprecated)]
use crate::event::WindowEvent::*; {
use crate::event::{PointerKind, PointerSource}; use crate::event::Event::*;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
use crate::window::WindowId;
with_window_event(CloseRequested); // Mainline events.
with_window_event(Destroyed); let wid = WindowId::from_raw(0);
with_window_event(Focused(true)); x(NewEvents(event::StartCause::Init));
with_window_event(Moved((0, 0).into())); x(AboutToWait);
with_window_event(SurfaceResized((0, 0).into())); x(LoopExiting);
with_window_event(DragEntered { paths: vec!["x.txt".into()], position: (0, 0).into() }); x(Suspended);
with_window_event(DragMoved { position: (0, 0).into() }); x(Resumed);
with_window_event(DragDropped { paths: vec!["x.txt".into()], position: (0, 0).into() });
with_window_event(DragLeft { position: Some((0, 0).into()) });
with_window_event(Ime(Enabled));
with_window_event(PointerMoved {
device_id: None,
primary: true,
position: (0, 0).into(),
source: PointerSource::Mouse,
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(PointerEntered {
device_id: None,
primary: true,
position: (0, 0).into(),
kind: PointerKind::Mouse,
});
with_window_event(PointerLeft {
primary: true,
device_id: None,
position: Some((0, 0).into()),
kind: PointerKind::Mouse,
});
with_window_event(MouseWheel {
device_id: None,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Pressed,
position: (0, 0).into(),
button: event::MouseButton::Other(0).into(),
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Released,
position: (0, 0).into(),
button: event::ButtonSource::Touch {
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
},
});
with_window_event(PinchGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: None });
with_window_event(RotationGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: None,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}};
(device: $closure:expr) => {{
use event::DeviceEvent::*;
#[allow(unused_mut)] // Window events.
let mut with_device_event: &mut dyn FnMut(event::DeviceEvent) = &mut $closure; let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
with_device_event(PointerMotion { delta: (0.0, 0.0).into() }); with_window_event(CloseRequested);
with_device_event(MouseWheel { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0) }); with_window_event(Destroyed);
with_device_event(Button { button: 0, state: event::ElementState::Pressed }); with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(SurfaceResized((0, 0).into()));
with_window_event(DroppedFile("x.txt".into()));
with_window_event(HoveredFile("x.txt".into()));
with_window_event(HoveredFileCancelled);
with_window_event(Ime(Enabled));
with_window_event(PointerMoved {
device_id: None,
primary: true,
position: (0, 0).into(),
source: PointerSource::Mouse,
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(PointerEntered {
device_id: None,
primary: true,
position: (0, 0).into(),
kind: PointerKind::Mouse,
});
with_window_event(PointerLeft {
primary: true,
device_id: None,
position: Some((0, 0).into()),
kind: PointerKind::Mouse,
});
with_window_event(MouseWheel {
device_id: None,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Pressed,
position: (0, 0).into(),
button: event::MouseButton::Other(0).into(),
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Released,
position: (0, 0).into(),
button: event::ButtonSource::Touch {
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
},
});
with_window_event(PinchGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: None });
with_window_event(RotationGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: None,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}
#[allow(deprecated)]
{
use event::DeviceEvent::*;
let with_device_event =
|dev_ev| x(event::Event::DeviceEvent { device_id: None, event: dev_ev });
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
}
}}; }};
} }
#[allow(clippy::clone_on_copy)] #[allow(clippy::redundant_clone)]
#[test] #[test]
fn test_event_clone() { fn test_event_clone() {
foreach_event!(|event| { foreach_event!(|event: event::Event| {
let event2 = event.clone(); let event2 = event.clone();
assert_eq!(event, event2); assert_eq!(event, event2);
}); })
} }
#[test] #[test]
@@ -1246,7 +1327,7 @@ mod tests {
#[allow(clippy::clone_on_copy)] #[allow(clippy::clone_on_copy)]
#[test] #[test]
fn ensure_attrs_do_not_panic() { fn ensure_attrs_do_not_panic() {
foreach_event!(|event| { foreach_event!(|event: event::Event| {
let _ = format!("{event:?}"); let _ = format!("{event:?}");
}); });
let _ = event::StartCause::Init.clone(); let _ = event::StartCause::Init.clone();

View File

@@ -25,7 +25,7 @@ use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError}; use crate::error::{EventLoopError, RequestError};
use crate::monitor::MonitorHandle; use crate::monitor::MonitorHandle;
use crate::platform_impl; use crate::platform_impl;
use crate::utils::{impl_dyn_casting, AsAny}; use crate::utils::AsAny;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes}; use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to /// Provides a way to retrieve events from the system and from the windows that were registered to
@@ -43,7 +43,6 @@ use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttri
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread. /// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
#[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
pub(crate) event_loop: platform_impl::EventLoop, pub(crate) event_loop: platform_impl::EventLoop,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
@@ -55,7 +54,7 @@ pub struct EventLoop {
/// easier. But note that constructing multiple event loops is not supported. /// easier. But note that constructing multiple event loops is not supported.
/// ///
/// This can be created using [`EventLoop::builder`]. /// This can be created using [`EventLoop::builder`].
#[derive(Default, Debug, PartialEq, Eq, Hash)] #[derive(Default, PartialEq, Eq, Hash)]
pub struct EventLoopBuilder { pub struct EventLoopBuilder {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
} }
@@ -118,6 +117,18 @@ impl EventLoopBuilder {
} }
} }
impl fmt::Debug for EventLoopBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopBuilder").finish_non_exhaustive()
}
}
impl fmt::Debug for EventLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoop").finish_non_exhaustive()
}
}
/// Set through [`ActiveEventLoop::set_control_flow()`]. /// Set through [`ActiveEventLoop::set_control_flow()`].
/// ///
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called. /// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
@@ -186,56 +197,7 @@ impl EventLoop {
impl EventLoop { impl EventLoop {
/// Run the application with the event loop on the calling thread. /// Run the application with the event loop on the calling thread.
/// ///
/// ## Event loop flow /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// This function internally handles the different parts of a traditional event-handling loop.
/// You can imagine this method as being implemented like this:
///
/// ```rust,ignore
/// let mut start_cause = StartCause::Init;
///
/// // Run the event loop.
/// while !event_loop.exiting() {
/// // Wake up.
/// app.new_events(event_loop, start_cause);
///
/// // Indicate that surfaces can now safely be created.
/// if start_cause == StartCause::Init {
/// app.can_create_surfaces(event_loop);
/// }
///
/// // Handle proxy wake-up event.
/// if event_loop.proxy_wake_up_set() {
/// event_loop.proxy_wake_up_clear();
/// app.proxy_wake_up(event_loop);
/// }
///
/// // Handle actions done by the user / system such as moving the cursor, resizing the
/// // window, changing the window theme, etc.
/// for event in event_loop.events() {
/// match event {
/// window event => app.window_event(event_loop, window_id, event),
/// device event => app.device_event(event_loop, device_id, event),
/// }
/// }
///
/// // Handle redraws.
/// for window_id in event_loop.pending_redraws() {
/// app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
/// }
///
/// // Done handling events, wait until we're woken up again.
/// app.about_to_wait(event_loop);
/// start_cause = event_loop.wait_if_necessary();
/// }
///
/// // Finished running, drop application state.
/// drop(app);
/// ```
///
/// This is of course a very coarse-grained overview, and leaves out timing details like
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
/// it should give you an idea of how things fit together.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
@@ -247,10 +209,10 @@ impl EventLoop {
/// ///
/// Web applications are recommended to use /// Web applications are recommended to use
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]" doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)] )]
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")] #[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to /// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to
/// make it clearer that the event loop runs asynchronously (via the browser's own, /// make it clearer that the event loop runs asynchronously (via the browser's own,
/// internal, event loop) and doesn't block the current thread of execution like it does /// internal, event loop) and doesn't block the current thread of execution like it does
@@ -347,7 +309,7 @@ impl AsRawFd for EventLoop {
} }
} }
pub trait ActiveEventLoop: AsAny + fmt::Debug { pub trait ActiveEventLoop: AsAny {
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread. /// to the main event loop, possibly from another thread.
fn create_proxy(&self) -> EventLoopProxy; fn create_proxy(&self) -> EventLoopProxy;
@@ -381,10 +343,10 @@ pub trait ActiveEventLoop: AsAny + fmt::Debug {
/// ///
/// **Web:** Only returns the current monitor without /// **Web:** Only returns the current monitor without
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)] )]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")] #[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>; fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
/// Returns the primary monitor of the system. /// Returns the primary monitor of the system.
@@ -396,10 +358,10 @@ pub trait ActiveEventLoop: AsAny + fmt::Debug {
/// - **Wayland:** Always returns `None`. /// - **Wayland:** Always returns `None`.
/// - **Web:** Always returns `None` without /// - **Web:** Always returns `None` without
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)] )]
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")] #[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
fn primary_monitor(&self) -> Option<MonitorHandle>; fn primary_monitor(&self) -> Option<MonitorHandle>;
/// Change if or when [`DeviceEvent`]s are captured. /// Change if or when [`DeviceEvent`]s are captured.
@@ -430,21 +392,14 @@ pub trait ActiveEventLoop: AsAny + fmt::Debug {
/// Gets the current [`ControlFlow`]. /// Gets the current [`ControlFlow`].
fn control_flow(&self) -> ControlFlow; fn control_flow(&self) -> ControlFlow;
/// Stop the event loop. /// This exits the event loop.
/// ///
/// ## Platform-specific /// See [`exiting`][crate::application::ApplicationHandler::exiting].
///
/// ### iOS
///
/// It is not possible to programmatically exit/quit an application on iOS, so this function is
/// a no-op there. See also [this technical Q&A][qa1561].
///
/// [qa1561]: https://developer.apple.com/library/archive/qa/qa1561/_index.html
fn exit(&self); fn exit(&self);
/// Returns whether the [`EventLoop`] is about to stop. /// Returns if the [`EventLoop`] is about to stop.
/// ///
/// Set by [`exit()`][Self::exit]. /// See [`exit()`][Self::exit].
fn exiting(&self) -> bool; fn exiting(&self) -> bool;
/// Gets a persistent reference to the underlying platform display. /// Gets a persistent reference to the underlying platform display.
@@ -462,8 +417,6 @@ impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
} }
} }
impl_dyn_casting!(ActiveEventLoop);
/// A proxy for the underlying display handle. /// A proxy for the underlying display handle.
/// ///
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying /// The purpose of this type is to provide a cheaply cloneable handle to the underlying
@@ -510,17 +463,23 @@ impl PartialEq for OwnedDisplayHandle {
impl Eq for OwnedDisplayHandle {} impl Eq for OwnedDisplayHandle {}
pub(crate) trait EventLoopProxyProvider: Send + Sync + fmt::Debug { pub(crate) trait EventLoopProxyProvider: Send + Sync {
/// See [`EventLoopProxy::wake_up`] for details. /// See [`EventLoopProxy::wake_up`] for details.
fn wake_up(&self); fn wake_up(&self);
} }
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly. /// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct EventLoopProxy { pub struct EventLoopProxy {
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>, pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
} }
impl fmt::Debug for EventLoopProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopProxy").finish_non_exhaustive()
}
}
impl EventLoopProxy { impl EventLoopProxy {
/// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being /// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
/// called. /// called.

View File

@@ -1,20 +1,7 @@
use std::error::Error; use std::error::Error;
use std::sync::Arc;
use std::{fmt, io, mem}; use std::{fmt, io, mem};
use crate::utils::{impl_dyn_casting, AsAny}; use crate::platform_impl::PlatformIcon;
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
/// An icon used for the window titlebar, taskbar, etc.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Icon(pub(crate) Arc<dyn IconProvider>);
// TODO remove that once split.
pub trait IconProvider: AsAny + fmt::Debug + Send + Sync {}
impl_dyn_casting!(IconProvider);
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
@@ -25,8 +12,10 @@ pub(crate) struct Pixel {
pub(crate) a: u8, pub(crate) a: u8,
} }
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
#[derive(Debug)] #[derive(Debug)]
/// An error produced when using [`RgbaIcon::new`] with invalid arguments. /// An error produced when using [`Icon::from_rgba`] with invalid arguments.
pub enum BadIcon { pub enum BadIcon {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels. /// safely interpreted as 32bpp RGBA pixels.
@@ -61,48 +50,69 @@ impl fmt::Display for BadIcon {
impl Error for BadIcon {} impl Error for BadIcon {}
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RgbaIcon { pub(crate) struct RgbaIcon {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u32, pub(crate) width: u32,
pub(crate) height: u32, pub(crate) height: u32,
pub(crate) rgba: Vec<u8>,
} }
impl RgbaIcon { /// For platforms which don't have window icons (e.g. Web)
pub fn new(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> { #[derive(Debug, Clone, PartialEq, Eq, Hash)]
if rgba.len() % PIXEL_SIZE != 0 { pub(crate) struct NoIcon;
return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
} #[allow(dead_code)] // These are not used on every platform
let pixel_count = rgba.len() / PIXEL_SIZE; mod constructors {
if pixel_count != (width * height) as usize { use super::*;
Err(BadIcon::DimensionsVsPixelCount {
width, impl RgbaIcon {
height, pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
width_x_height: (width * height) as usize, if rgba.len() % PIXEL_SIZE != 0 {
pixel_count, return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
}) }
} else { let pixel_count = rgba.len() / PIXEL_SIZE;
Ok(RgbaIcon { rgba, width, height }) if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
})
} else {
Ok(RgbaIcon { rgba, width, height })
}
} }
} }
pub fn width(&self) -> u32 { impl NoIcon {
self.width pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
} // Create the rgba icon anyway to validate the input
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
pub fn height(&self) -> u32 { Ok(NoIcon)
self.height }
}
pub fn buffer(&self) -> &[u8] {
self.rgba.as_slice()
} }
} }
impl IconProvider for RgbaIcon {} /// An icon used for the window titlebar, taskbar, etc.
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Icon {
pub(crate) inner: PlatformIcon,
}
impl From<RgbaIcon> for Icon { impl fmt::Debug for Icon {
fn from(value: RgbaIcon) -> Self { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
Self(Arc::new(value)) fmt::Debug::fmt(&self.inner, formatter)
}
}
impl Icon {
/// 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<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered();
Ok(Icon { inner: PlatformIcon::from_rgba(rgba, width, height)? })
} }
} }

View File

@@ -1,5 +1,4 @@
//! Types related to the keyboard. //! Types related to the keyboard.
#![cfg_attr(feature = "serde", allow(deprecated))] // https://github.com/serde-rs/serde/issues/2195
// This file contains a substantial portion of the UI Events Specification by the W3C. In // This file contains a substantial portion of the UI Events Specification by the W3C. In
// particular, the variant names within `Key` and `KeyCode` and their documentation are modified // particular, the variant names within `Key` and `KeyCode` and their documentation are modified
@@ -284,7 +283,11 @@ impl PartialEq<PhysicalKey> for NativeKeyCode {
/// Code representing the location of a physical key /// Code representing the location of a physical key
/// ///
/// This conforms to the UI Events Specification's [`KeyboardEvent.code`]. /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
/// exceptions:
/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and
/// "SuperRight" here.
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
/// ///
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables /// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
#[non_exhaustive] #[non_exhaustive]
@@ -417,7 +420,7 @@ pub enum KeyCode {
/// <kbd>CapsLock</kbd> or <kbd>⇪</kbd> /// <kbd>CapsLock</kbd> or <kbd>⇪</kbd>
CapsLock, CapsLock,
/// The application context menu key, which is typically found between the right /// The application context menu key, which is typically found between the right
/// <kbd>Meta</kbd> key and the right <kbd>Control</kbd> key. /// <kbd>Super</kbd> key and the right <kbd>Control</kbd> key.
ContextMenu, ContextMenu,
/// <kbd>Control</kbd> or <kbd>⌃</kbd> /// <kbd>Control</kbd> or <kbd>⌃</kbd>
ControlLeft, ControlLeft,
@@ -426,9 +429,9 @@ pub enum KeyCode {
/// <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards. /// <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards.
Enter, Enter,
/// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key. /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
MetaLeft, SuperLeft,
/// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key. /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
MetaRight, SuperRight,
/// <kbd>Shift</kbd> or <kbd>⇧</kbd> /// <kbd>Shift</kbd> or <kbd>⇧</kbd>
ShiftLeft, ShiftLeft,
/// <kbd>Shift</kbd> or <kbd>⇧</kbd> /// <kbd>Shift</kbd> or <kbd>⇧</kbd>
@@ -610,11 +613,9 @@ pub enum KeyCode {
AudioVolumeMute, AudioVolumeMute,
AudioVolumeUp, AudioVolumeUp,
WakeUp, WakeUp,
// Legacy modifier key. Also called "Super" in certain places.
Meta,
// Legacy modifier key. // Legacy modifier key.
#[deprecated = "marked as legacy in the spec, use Meta instead"]
Super,
// Legacy modifier key.
#[deprecated = "marked as legacy in the spec, use Meta instead"]
Hyper, Hyper,
Turbo, Turbo,
Abort, Abort,
@@ -740,7 +741,12 @@ pub enum KeyCode {
/// A [`Key::Named`] value /// A [`Key::Named`] value
/// ///
/// This conforms to the UI Events Specification's [`KeyboardEvent.key`]. /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few
/// exceptions:
/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's
/// another key which the specification calls `Super`. That does not exist here.)
/// - The `Space` variant here, can be identified by the character it generates in the
/// specification.
/// ///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ /// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[non_exhaustive] #[non_exhaustive]
@@ -785,22 +791,24 @@ pub enum NamedKey {
/// The Symbol modifier key (used on some virtual keyboards). /// The Symbol modifier key (used on some virtual keyboards).
Symbol, Symbol,
SymbolLock, SymbolLock,
// Legacy modifier key. Also called "Super" in certain places.
Meta,
// Legacy modifier key. // Legacy modifier key.
#[deprecated = "marked as legacy in the spec, use Meta instead"]
Super,
// Legacy modifier key.
#[deprecated = "marked as legacy in the spec, use Meta instead"]
Hyper, Hyper,
/// Used to enable "meta" modifier function for interpreting concurrent or subsequent keyboard /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard
/// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘`
/// key. /// key.
Meta, ///
/// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key.
Super,
/// The `Enter` or `↵` key. Used to activate current selection or accept current input. This /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This
/// key value is also used for the `Return` (Macintosh numpad) key. This key value is also /// key value is also used for the `Return` (Macintosh numpad) key. This key value is also
/// used for the Android `KEYCODE_DPAD_CENTER`. /// used for the Android `KEYCODE_DPAD_CENTER`.
Enter, Enter,
/// The Horizontal Tabulation `Tab` key. /// The Horizontal Tabulation `Tab` key.
Tab, Tab,
/// Used in text to insert a space between words. Usually located below the character keys.
Space,
/// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`)
ArrowDown, ArrowDown,
/// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`)
@@ -856,7 +864,7 @@ pub enum NamedKey {
Attn, Attn,
Cancel, Cancel,
/// Show the applications context menu. /// Show the applications context menu.
/// This key is commonly found between the right `Meta` key and the right `Control` key. /// This key is commonly found between the right `Super` key and the right `Control` key.
ContextMenu, ContextMenu,
/// The `Esc` key. This key was originally used to initiate an escape sequence, but is /// The `Esc` key. This key was originally used to initiate an escape sequence, but is
/// now more generally used to exit or "escape" the current context, such as closing a dialog /// now more generally used to exit or "escape" the current context, such as closing a dialog
@@ -1575,6 +1583,7 @@ impl NamedKey {
NamedKey::Enter => Some("\r"), NamedKey::Enter => Some("\r"),
NamedKey::Backspace => Some("\x08"), NamedKey::Backspace => Some("\x08"),
NamedKey::Tab => Some("\t"), NamedKey::Tab => Some("\t"),
NamedKey::Space => Some(" "),
NamedKey::Escape => Some("\x1b"), NamedKey::Escape => Some("\x1b"),
_ => None, _ => None,
} }
@@ -1700,9 +1709,7 @@ bitflags! {
/// The "alt" key. /// The "alt" key.
const ALT = 0b100 << 6; const ALT = 0b100 << 6;
/// This is the "windows" key on PC and "command" key on Mac. /// This is the "windows" key on PC and "command" key on Mac.
const META = 0b100 << 9; const SUPER = 0b100 << 9;
#[deprecated = "use META instead"]
const SUPER = Self::META.bits();
} }
} }
@@ -1722,9 +1729,9 @@ impl ModifiersState {
self.intersects(Self::ALT) self.intersects(Self::ALT)
} }
/// Returns `true` if the meta key is pressed. /// Returns `true` if the super key is pressed.
pub fn meta_key(&self) -> bool { pub fn super_key(&self) -> bool {
self.intersects(Self::META) self.intersects(Self::SUPER)
} }
} }
@@ -1757,11 +1764,7 @@ bitflags! {
const RCONTROL = 0b0000_1000; const RCONTROL = 0b0000_1000;
const LALT = 0b0001_0000; const LALT = 0b0001_0000;
const RALT = 0b0010_0000; const RALT = 0b0010_0000;
const LMETA = 0b0100_0000; const LSUPER = 0b0100_0000;
const RMETA = 0b1000_0000; const RSUPER = 0b1000_0000;
#[deprecated = "use LMETA instead"]
const LSUPER = Self::LMETA.bits();
#[deprecated = "use RMETA instead"]
const RSUPER = Self::RMETA.bits();
} }
} }

View File

@@ -1,5 +1,59 @@
//! Winit is a cross-platform window creation and event loop management library. //! Winit is a cross-platform window creation and event loop management library.
//! //!
//! # Usage
//!
//! `winit` can be added to `Cargo.toml` as a dependency. It can be added via `cargo add`.
//!
//! ```bash
//! $ cargo add winit
//! ```
//!
//! To only enable the X11 backend on Free Unix[^unix] systems, disable default features
//! and enable the `x11` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features x11
//! ```
//!
//! To only enable the Wayland backend on Free Unix systems, disable default features
//! and enable the `wayland` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features wayland
//! ```
//!
//! These features have no effect on systems that are not Free Unix.
//!
//! ## Dependencies
//!
//! Dependencies on non-system libraries is managed through Cargo. For the X11
//! backend, the following Ubuntu packages or their equivalents must[^must] be installed.
//!
//! - `libx11-dev`
//! - `libxcb1-dev`
//! - `libxi-dev`
//! - `libxcbcommon-dev`
//! - `libxcbcommon-x11-dev`
//!
//! For the Wayland backend, the following Ubuntu packages or their equivalents
//! must be installed.
//!
//! - `libwayland-dev`
//! - `libxcbcommon-dev`
//! - `libfontconfig` (only with `sctk-adwaita` feature)
//! - `freetype` (only with `sctk-adwaita` feature)
//!
//! The "dev" packages are only needed for building binaries that use `winit`. On
//! deployed system the non-`dev` equivalents need to be installed.
//!
//! The other backends (Windows, macOS, etc) do not have any dependencies on system libraries
//! that don't already come with the operating system. However, note that the Windows backend
//! only supports Windows 10 and above, and the macOS backend only supports macOS
//! 10.14 and above.
//!
//! [^unix]: Unix systems outside of Android and Apple, like Linux or FreeBSD.
//! [^must]: This is not a "must" when the "dlopen" features are enabled
//!
//! # Building windows //! # Building windows
//! //!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with //! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
@@ -26,8 +80,9 @@
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. //! [`DeviceEvent`].
//! //!
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will dispatch events //! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! for every [`Window`] that was created with that particular [`EventLoop`]. //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//! //!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works //! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
@@ -57,19 +112,10 @@
//! //!
//! impl ApplicationHandler for App { //! impl ApplicationHandler for App {
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { //! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
//! // The event loop has launched, and we can initialize our UI state.
//!
//! // Create a simple window with default attributes.
//! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap()); //! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
//! } //! }
//! //!
//! fn window_event( //! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
//! &mut self,
//! event_loop: &dyn ActiveEventLoop,
//! id: WindowId,
//! event: WindowEvent,
//! ) {
//! // Called by `EventLoop::run_app` when a new event happens on the window.
//! match event { //! match event {
//! WindowEvent::CloseRequested => { //! WindowEvent::CloseRequested => {
//! println!("The close button was pressed; stopping"); //! println!("The close button was pressed; stopping");
@@ -90,18 +136,15 @@
//! // applications which do not always need to. Applications that redraw continuously //! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead. //! // can render here instead.
//! self.window.as_ref().unwrap().request_redraw(); //! self.window.as_ref().unwrap().request_redraw();
//! }, //! }
//! _ => (), //! _ => (),
//! } //! }
//! } //! }
//! } //! }
//! //!
//! # // Intentionally use `fn main` for clarity //! # // Intentionally use `fn main` for clarity
//! fn main() -> Result<(), Box<dyn std::error::Error>> { //! fn main() {
//! // Create a new event loop. //! let event_loop = EventLoop::new().unwrap();
//! let event_loop = EventLoop::new()?;
//!
//! // Configure settings before launching.
//! //!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications. //! // dispatched any events. This is ideal for games and similar applications.
@@ -112,10 +155,8 @@
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. //! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait); //! event_loop.set_control_flow(ControlFlow::Wait);
//! //!
//! // Launch and begin running the event loop. //! let mut app = App::default();
//! event_loop.run_app(App::default())?; //! event_loop.run_app(&mut app);
//!
//! Ok(())
//! } //! }
//! ``` //! ```
//! //!
@@ -270,10 +311,13 @@
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes //! [`WindowAttributes`]: window::WindowAttributes
//! [window_new]: window::Window::new
//! [`create_window`]: event_loop::ActiveEventLoop::create_window //! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [`Window::id()`]: window::Window::id //! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent //! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland. //! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
@@ -288,8 +332,6 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
#![warn(clippy::uninlined_format_args)] #![warn(clippy::uninlined_format_args)]
// TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it.
#![cfg_attr(web_platform, allow(unknown_lints, wasm_c_abi))]
// Re-export DPI types so that users don't have to put it in Cargo.toml. // Re-export DPI types so that users don't have to put it in Cargo.toml.
#[doc(inline)] #[doc(inline)]
@@ -304,7 +346,7 @@ pub mod error;
mod cursor; mod cursor;
pub mod event; pub mod event;
pub mod event_loop; pub mod event_loop;
pub mod icon; mod icon;
pub mod keyboard; pub mod keyboard;
pub mod monitor; pub mod monitor;
mod platform_impl; mod platform_impl;

View File

@@ -1,18 +1,88 @@
//! Types useful for interacting with a user's monitors. //! Types useful for interacting with a user's 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`]:
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
use std::borrow::Cow;
use std::fmt;
use std::num::{NonZeroU16, NonZeroU32}; use std::num::{NonZeroU16, NonZeroU32};
use std::ops::Deref;
use std::sync::Arc;
use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::utils::{impl_dyn_casting, AsAny}; use crate::platform_impl;
/// A handle to a fullscreen video mode of a specific monitor.
///
/// This can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
}
impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f)
}
}
impl PartialOrd for VideoModeHandle {
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VideoModeHandle {
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
self.monitor().cmp(&other.monitor()).then(
self.size()
.cmp(&other.size())
.then(
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
)
}
}
impl VideoModeHandle {
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::surface_size()`]: crate::window::Window::surface_size
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.video_mode.size()
}
/// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not.
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.video_mode.bit_depth()
}
/// Returns the refresh rate of this video mode in mHz.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.video_mode.refresh_rate_millihertz()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.video_mode.monitor() }
}
}
impl std::fmt::Display for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} {}{}",
self.size().width,
self.size().height,
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
)
}
}
/// Handle to a monitor. /// Handle to a monitor.
/// ///
@@ -30,72 +100,48 @@ use crate::utils::{impl_dyn_casting, AsAny};
/// ///
/// **Web:** A [`MonitorHandle`] created without /// **Web:** A [`MonitorHandle`] created without
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)] )]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")] #[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
/// will always represent the current monitor the browser window is in instead of a specific /// will always represent the current monitor the browser window is in instead of a specific
/// monitor. See /// monitor. See
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]" doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]"
)] )]
#[cfg_attr(not(web_platform), doc = "`MonitorHandleExtWeb::is_detailed()`")] #[cfg_attr(not(any(web_platform, docsrs)), doc = "`MonitorHandleExtWeb::is_detailed()`")]
/// to check. /// to check.
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
#[derive(Debug, Clone)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle(pub(crate) Arc<dyn MonitorHandleProvider>); pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle,
}
impl Deref for MonitorHandle { impl std::fmt::Debug for MonitorHandle {
type Target = dyn MonitorHandleProvider; fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
fn deref(&self) -> &Self::Target {
self.0.as_ref()
} }
} }
impl PartialEq for MonitorHandle { impl MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.0.as_ref().eq(other.0.as_ref())
}
}
impl Eq for MonitorHandle {}
/// Provider of the [`MonitorHandle`].
pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
/// Identifier for this monitor.
///
/// The representation of this modifier is not guaranteed and should be used only to compare
/// monitors.
fn id(&self) -> u128;
/// Native platform identifier of this monitor.
///
/// # Platform-specific
///
/// - **Windows**: This is `HMONITOR`.
/// - **macOS**: This is `CGDirectDisplayID`.
/// - **iOS**: This is `UIScreen*`.
/// - **Wayland**: This is the ID of the `wl_output` device.
/// - **X11**: This is the ID of the CRTC.
/// - **Web**: This is an internal ID not meant for consumption.
fn native_id(&self) -> u64;
/// Returns a human-readable name of the monitor. /// Returns a human-readable name of the monitor.
/// ///
/// Returns `None` if the monitor doesn't exist anymore or the name couldn't be obtained. /// Returns `None` if the monitor doesn't exist anymore.
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// **Web:** Always returns [`None`] without /// **Web:** Always returns [`None`] without
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)] )]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")] #[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
fn name(&self) -> Option<Cow<'_, str>>; #[inline]
pub fn name(&self) -> Option<String> {
self.inner.name()
}
/// Returns the top-left corner position of the monitor in desktop coordinates. /// Returns the top-left corner position of the monitor in desktop coordinates.
/// ///
@@ -107,11 +153,14 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
/// ///
/// **Web:** Always returns [`None`] without /// **Web:** Always returns [`None`] without
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)] )]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")] #[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
fn position(&self) -> Option<PhysicalPosition<i32>>; #[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
self.inner.position()
}
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical /// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`]. /// pixels and vice versa, use [`Window::scale_factor`].
@@ -125,81 +174,27 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
/// - **Android:** Always returns 1.0. /// - **Android:** Always returns 1.0.
/// - **Web:** Always returns `0.0` without /// - **Web:** Always returns `0.0` without
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)] )]
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")] #[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
/// ///
#[rustfmt::skip] #[rustfmt::skip]
/// [`Window::scale_factor`]: crate::window::Window::scale_factor /// [`Window::scale_factor`]: crate::window::Window::scale_factor
fn scale_factor(&self) -> f64; #[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()
}
fn current_video_mode(&self) -> Option<VideoMode>; /// Returns the currently active video mode of this monitor.
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
}
/// Returns all fullscreen video modes supported by this monitor. /// Returns all fullscreen video modes supported by this monitor.
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>>; #[inline]
} pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
impl PartialEq for dyn MonitorHandleProvider + '_ {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
} }
} }
impl Eq for dyn MonitorHandleProvider + '_ {}
impl_dyn_casting!(MonitorHandleProvider);
/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with [`MonitorHandleProvider::video_modes`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: Option<NonZeroU16>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
}
impl VideoMode {
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::surface_size()`]: crate::window::Window::surface_size
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
/// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not.
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}
/// Returns the refresh rate of this video mode in mHz.
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
}
impl fmt::Display for VideoMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}x{} {}{}",
self.size.width,
self.size.height,
self.refresh_rate_millihertz.map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
self.bit_depth.map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
)
}
}
/// Fullscreen modes.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Fullscreen {
Exclusive(MonitorHandle, VideoMode),
/// Providing `None` to `Borderless` will fullscreen on the current monitor.
Borderless(Option<MonitorHandle>),
}

View File

@@ -62,7 +62,7 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building //! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be: //! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml` //! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.10", //! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5",
//! features = [ "android-native-activity" ] }` //! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
@@ -101,19 +101,20 @@ pub trait WindowExtAndroid {
impl WindowExtAndroid for dyn Window + '_ { impl WindowExtAndroid for dyn Window + '_ {
fn content_rect(&self) -> Rect { fn content_rect(&self) -> Rect {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.content_rect() window.content_rect()
} }
fn config(&self) -> ConfigurationRef { fn config(&self) -> ConfigurationRef {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.config() window.config()
} }
} }
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp { fn android_app(&self) -> &AndroidApp {
let event_loop = self.cast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap(); let event_loop =
self.as_any().downcast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();
&event_loop.app &event_loop.app
} }
} }

View File

@@ -85,9 +85,12 @@
//! //!
//! - applicationDidBecomeActive is Resumed //! - applicationDidBecomeActive is Resumed
//! - applicationWillResignActive is Suspended //! - applicationWillResignActive is Suspended
//! - applicationWillTerminate corresponds to `Drop`ping the application handler. //! - applicationWillTerminate is LoopExiting
//! //!
//! Note that an app may not receive the `Drop` event if suspended; it might be SIGKILL'ed. //! Keep in mind that after LoopExiting event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
//! //!
//! ## Custom `UIApplicationDelegate` //! ## Custom `UIApplicationDelegate`
//! //!
@@ -104,8 +107,7 @@ use std::os::raw::c_void;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::monitor::{MonitorHandle, VideoMode}; use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::platform_impl::MonitorHandle as IosMonitorHandle;
use crate::window::{Window, WindowAttributes}; use crate::window::{Window, WindowAttributes};
/// Additional methods on [`Window`] that are specific to iOS. /// Additional methods on [`Window`] that are specific to iOS.
@@ -113,11 +115,10 @@ pub trait WindowExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// ///
/// The default value is device dependent, and it's recommended GLES or Metal applications set /// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandleProvider::scale_factor()`]. /// this to [`MonitorHandle::scale_factor()`].
/// ///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
fn set_scale_factor(&self, scale_factor: f64); fn set_scale_factor(&self, scale_factor: f64);
/// Sets the valid orientations for the [`Window`]. /// Sets the valid orientations for the [`Window`].
@@ -211,25 +212,25 @@ pub trait WindowExtIOS {
impl WindowExtIOS for dyn Window + '_ { impl WindowExtIOS for dyn Window + '_ {
#[inline] #[inline]
fn set_scale_factor(&self, scale_factor: f64) { fn set_scale_factor(&self, scale_factor: f64) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_scale_factor(scale_factor)); window.maybe_wait_on_main(move |w| w.set_scale_factor(scale_factor));
} }
#[inline] #[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_valid_orientations(valid_orientations)); window.maybe_wait_on_main(move |w| w.set_valid_orientations(valid_orientations));
} }
#[inline] #[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) { fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden)); window.maybe_wait_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden));
} }
#[inline] #[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| { window.maybe_wait_on_main(move |w| {
w.set_preferred_screen_edges_deferring_system_gestures(edges) w.set_preferred_screen_edges_deferring_system_gestures(edges)
}); });
@@ -237,19 +238,19 @@ impl WindowExtIOS for dyn Window + '_ {
#[inline] #[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) { fn set_prefers_status_bar_hidden(&self, hidden: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_prefers_status_bar_hidden(hidden)); window.maybe_wait_on_main(move |w| w.set_prefers_status_bar_hidden(hidden));
} }
#[inline] #[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) { fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style)) window.maybe_wait_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
} }
#[inline] #[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) { fn recognize_pinch_gesture(&self, should_recognize: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_pinch_gesture(should_recognize)); window.maybe_wait_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
} }
@@ -260,7 +261,7 @@ impl WindowExtIOS for dyn Window + '_ {
minimum_number_of_touches: u8, minimum_number_of_touches: u8,
maximum_number_of_touches: u8, maximum_number_of_touches: u8,
) { ) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| { window.maybe_wait_on_main(move |w| {
w.recognize_pan_gesture( w.recognize_pan_gesture(
should_recognize, should_recognize,
@@ -272,13 +273,13 @@ impl WindowExtIOS for dyn Window + '_ {
#[inline] #[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) { fn recognize_doubletap_gesture(&self, should_recognize: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_doubletap_gesture(should_recognize)); window.maybe_wait_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
} }
#[inline] #[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) { fn recognize_rotation_gesture(&self, should_recognize: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_rotation_gesture(should_recognize)); window.maybe_wait_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
} }
} }
@@ -288,11 +289,10 @@ pub trait WindowAttributesExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
/// ///
/// The default value is device dependent, and it's recommended GLES or Metal applications set /// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandleProvider::scale_factor()`]. /// this to [`MonitorHandle::scale_factor()`].
/// ///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
fn with_scale_factor(self, scale_factor: f64) -> Self; fn with_scale_factor(self, scale_factor: f64) -> Self;
/// Sets the valid orientations for the [`Window`]. /// Sets the valid orientations for the [`Window`].
@@ -384,25 +384,23 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void; fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoMode`] for this monitor. /// Returns the preferred [`VideoModeHandle`] for this monitor.
/// ///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoMode; fn preferred_video_mode(&self) -> VideoModeHandle;
} }
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen // SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
let monitor = self.cast_ref::<IosMonitorHandle>().unwrap(); objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
objc2::rc::Retained::as_ptr(monitor.ui_screen(mtm)) as *mut c_void
} }
#[inline] #[inline]
fn preferred_video_mode(&self) -> VideoMode { fn preferred_video_mode(&self) -> VideoModeHandle {
let monitor = self.cast_ref::<IosMonitorHandle>().unwrap(); VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
monitor.preferred_video_mode()
} }
} }

View File

@@ -21,21 +21,26 @@
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")] #![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained; //! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject; //! use objc2::runtime::ProtocolObject;
//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly}; //! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; //! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol}; //! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop; //! use winit::event_loop::EventLoop;
//! //!
//! define_class!( //! declare_class!(
//! #[unsafe(super(NSObject))]
//! #[thread_kind = MainThreadOnly]
//! #[name = "AppDelegate"]
//! struct AppDelegate; //! struct AppDelegate;
//! //!
//! unsafe impl ClassType for AppDelegate {
//! type Super = NSObject;
//! type Mutability = mutability::MainThreadOnly;
//! const NAME: &'static str = "MyAppDelegate";
//! }
//!
//! impl DeclaredClass for AppDelegate {}
//!
//! unsafe impl NSObjectProtocol for AppDelegate {} //! unsafe impl NSObjectProtocol for AppDelegate {}
//! //!
//! unsafe impl NSApplicationDelegate for AppDelegate { //! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[unsafe(method(application:openURLs:))] //! #[method(application:openURLs:)]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) { //! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_ //! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example. //! // have to bundle your application. This is not done in this example.
@@ -46,7 +51,7 @@
//! //!
//! impl AppDelegate { //! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> { //! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] } //! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
//! } //! }
//! } //! }
//! //!
@@ -73,7 +78,6 @@ use serde::{Deserialize, Serialize};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder}; use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle; use crate::monitor::MonitorHandle;
use crate::platform_impl::MonitorHandle as MacOsMonitorHandle;
use crate::window::{Window, WindowAttributes, WindowId}; use crate::window::{Window, WindowAttributes, WindowId};
/// Additional methods on [`Window`] that are specific to MacOS. /// Additional methods on [`Window`] that are specific to MacOS.
@@ -151,9 +155,7 @@ pub trait WindowExtMacOS {
/// Getter for the [`WindowExtMacOS::set_option_as_alt`]. /// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt; fn option_as_alt(&self) -> OptionAsAlt;
/// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games. /// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games.
/// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
/// [`Window::set_fullscreen`] is called.
fn set_borderless_game(&self, borderless_game: bool); fn set_borderless_game(&self, borderless_game: bool);
/// Getter for the [`WindowExtMacOS::set_borderless_game`]. /// Getter for the [`WindowExtMacOS::set_borderless_game`].
@@ -170,109 +172,109 @@ pub trait WindowExtMacOS {
impl WindowExtMacOS for dyn Window + '_ { impl WindowExtMacOS for dyn Window + '_ {
#[inline] #[inline]
fn simple_fullscreen(&self) -> bool { fn simple_fullscreen(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.simple_fullscreen()) window.maybe_wait_on_main(|w| w.simple_fullscreen())
} }
#[inline] #[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen)) window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
} }
#[inline] #[inline]
fn has_shadow(&self) -> bool { fn has_shadow(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.has_shadow()) window.maybe_wait_on_main(|w| w.has_shadow())
} }
#[inline] #[inline]
fn set_has_shadow(&self, has_shadow: bool) { fn set_has_shadow(&self, has_shadow: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow)); window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow));
} }
#[inline] #[inline]
fn set_tabbing_identifier(&self, identifier: &str) { fn set_tabbing_identifier(&self, identifier: &str) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier)) window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
} }
#[inline] #[inline]
fn tabbing_identifier(&self) -> String { fn tabbing_identifier(&self) -> String {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.tabbing_identifier()) window.maybe_wait_on_main(|w| w.tabbing_identifier())
} }
#[inline] #[inline]
fn select_next_tab(&self) { fn select_next_tab(&self) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.select_next_tab()); window.maybe_wait_on_main(|w| w.select_next_tab());
} }
#[inline] #[inline]
fn select_previous_tab(&self) { fn select_previous_tab(&self) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.select_previous_tab()); window.maybe_wait_on_main(|w| w.select_previous_tab());
} }
#[inline] #[inline]
fn select_tab_at_index(&self, index: usize) { fn select_tab_at_index(&self, index: usize) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.select_tab_at_index(index)); window.maybe_wait_on_main(move |w| w.select_tab_at_index(index));
} }
#[inline] #[inline]
fn num_tabs(&self) -> usize { fn num_tabs(&self) -> usize {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.num_tabs()) window.maybe_wait_on_main(|w| w.num_tabs())
} }
#[inline] #[inline]
fn is_document_edited(&self) -> bool { fn is_document_edited(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.is_document_edited()) window.maybe_wait_on_main(|w| w.is_document_edited())
} }
#[inline] #[inline]
fn set_document_edited(&self, edited: bool) { fn set_document_edited(&self, edited: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_document_edited(edited)); window.maybe_wait_on_main(move |w| w.set_document_edited(edited));
} }
#[inline] #[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt)); window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt));
} }
#[inline] #[inline]
fn option_as_alt(&self) -> OptionAsAlt { fn option_as_alt(&self) -> OptionAsAlt {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.option_as_alt()) window.maybe_wait_on_main(|w| w.option_as_alt())
} }
#[inline] #[inline]
fn set_borderless_game(&self, borderless_game: bool) { fn set_borderless_game(&self, borderless_game: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game)) window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
} }
#[inline] #[inline]
fn is_borderless_game(&self) -> bool { fn is_borderless_game(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.is_borderless_game()) window.maybe_wait_on_main(|w| w.is_borderless_game())
} }
#[inline] #[inline]
fn set_unified_titlebar(&self, unified_titlebar: bool) { fn set_unified_titlebar(&self, unified_titlebar: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar)) window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
} }
#[inline] #[inline]
fn unified_titlebar(&self) -> bool { fn unified_titlebar(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.unified_titlebar()) window.maybe_wait_on_main(|w| w.unified_titlebar())
} }
} }
@@ -330,13 +332,6 @@ pub trait WindowAttributesExtMacOS {
fn with_borderless_game(self, borderless_game: bool) -> Self; fn with_borderless_game(self, borderless_game: bool) -> Self;
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set. /// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self; fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
/// [`NSWindow`].
///
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?language=objc
/// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
fn with_panel(self, panel: bool) -> Self;
} }
impl WindowAttributesExtMacOS for WindowAttributes { impl WindowAttributesExtMacOS for WindowAttributes {
@@ -417,12 +412,6 @@ impl WindowAttributesExtMacOS for WindowAttributes {
self.platform_specific.unified_titlebar = unified_titlebar; self.platform_specific.unified_titlebar = unified_titlebar;
self self
} }
#[inline]
fn with_panel(mut self, panel: bool) -> Self {
self.platform_specific.panel = panel;
self
}
} }
pub trait EventLoopBuilderExtMacOS { pub trait EventLoopBuilderExtMacOS {
@@ -502,16 +491,22 @@ 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 { pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
/// Returns a pointer to the NSScreen representing this monitor. /// Returns a pointer to the NSScreen representing this monitor.
fn ns_screen(&self) -> Option<*mut c_void>; fn ns_screen(&self) -> Option<*mut c_void>;
} }
impl MonitorHandleExtMacOS for MonitorHandle { impl MonitorHandleExtMacOS for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
let monitor = self.cast_ref::<MacOsMonitorHandle>().unwrap();
// SAFETY: We only use the marker to get a pointer // SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _) self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
} }
} }
@@ -534,28 +529,32 @@ pub trait ActiveEventLoopExtMacOS {
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
fn hide_application(&self) { fn hide_application(&self) {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS"); .expect("non macOS event loop on macOS");
event_loop.hide_application() event_loop.hide_application()
} }
fn hide_other_applications(&self) { fn hide_other_applications(&self) {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS"); .expect("non macOS event loop on macOS");
event_loop.hide_other_applications() event_loop.hide_other_applications()
} }
fn set_allows_automatic_window_tabbing(&self, enabled: bool) { fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS"); .expect("non macOS event loop on macOS");
event_loop.set_allows_automatic_window_tabbing(enabled); event_loop.set_allows_automatic_window_tabbing(enabled);
} }
fn allows_automatic_window_tabbing(&self) -> bool { fn allows_automatic_window_tabbing(&self) -> bool {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS"); .expect("non macOS event loop on macOS");
event_loop.allows_automatic_window_tabbing() event_loop.allows_automatic_window_tabbing()
} }

View File

@@ -2,23 +2,23 @@
//! //!
//! Only the modules corresponding to the platform you're compiling to will be available. //! Only the modules corresponding to the platform you're compiling to will be available.
#[cfg(android_platform)] #[cfg(any(android_platform, docsrs))]
pub mod android; pub mod android;
#[cfg(ios_platform)] #[cfg(any(ios_platform, docsrs))]
pub mod ios; pub mod ios;
#[cfg(macos_platform)] #[cfg(any(macos_platform, docsrs))]
pub mod macos; pub mod macos;
#[cfg(orbital_platform)] #[cfg(any(orbital_platform, docsrs))]
pub mod orbital; pub mod orbital;
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform, docsrs))]
pub mod startup_notify; pub mod startup_notify;
#[cfg(wayland_platform)] #[cfg(any(wayland_platform, docsrs))]
pub mod wayland; pub mod wayland;
#[cfg(web_platform)] #[cfg(any(web_platform, docsrs))]
pub mod web; pub mod web;
#[cfg(windows_platform)] #[cfg(any(windows_platform, docsrs))]
pub mod windows; pub mod windows;
#[cfg(x11_platform)] #[cfg(any(x11_platform, docsrs))]
pub mod x11; pub mod x11;
#[allow(unused_imports)] #[allow(unused_imports)]
@@ -42,5 +42,15 @@ pub mod run_on_demand;
))] ))]
pub mod pump_events; pub mod pump_events;
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
orbital_platform,
docsrs
))]
pub mod modifier_supplement;
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))] #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
pub mod scancode; pub mod scancode;

View File

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

View File

@@ -47,19 +47,19 @@ pub trait EventLoopExtPumpEvents {
/// buffered and handled outside of Winit include: /// buffered and handled outside of Winit include:
/// - `RedrawRequested` events, used to schedule rendering. /// - `RedrawRequested` events, used to schedule rendering.
/// ///
/// macOS for example uses a `drawRect` callback to drive rendering /// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before /// within applications and expects rendering to be finished before
/// the `drawRect` callback returns. /// the `drawRect` callback returns.
/// ///
/// For portability it's strongly recommended that applications should /// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit. /// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`. /// - Any lifecycle events, such as `Suspended` / `Resumed`.
/// ///
/// The handling of these events needs to be synchronized with the /// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a /// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and /// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that /// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered. /// other lifecycle events occur while the event is buffered.
/// ///
/// ## Supported Platforms /// ## Supported Platforms
/// ///

View File

@@ -33,10 +33,10 @@ pub trait EventLoopExtRunOnDemand {
/// to the caller (specifically this is impossible on iOS and Web - though with the Web /// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use /// backend it is possible to use
#[cfg_attr( #[cfg_attr(
web_platform, any(web_platform, docsrs),
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]" doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)] )]
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")] #[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] more than once instead). /// [^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop. /// - No [`Window`] state can be carried between separate runs of the event loop.
/// ///

View File

@@ -65,9 +65,9 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
let _is_wayland = self.is_wayland(); let _is_wayland = self.is_wayland();
if _is_wayland { if _is_wayland {
env::var(WAYLAND_VAR).ok().map(ActivationToken::from_raw) env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
} else { } else {
env::var(X11_VAR).ok().map(ActivationToken::from_raw) env::var(X11_VAR).ok().map(ActivationToken::_new)
} }
} }
} }
@@ -75,12 +75,15 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
impl WindowExtStartupNotify for dyn Window + '_ { impl WindowExtStartupNotify for dyn Window + '_ {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> { fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
if let Some(window) = self.cast_ref::<crate::platform_impl::wayland::Window>() { if let Some(window) = self.as_any().downcast_ref::<crate::platform_impl::wayland::Window>()
{
return window.request_activation_token(); return window.request_activation_token();
} }
#[cfg(x11_platform)] #[cfg(x11_platform)]
if let Some(window) = self.cast_ref::<crate::platform_impl::x11::window::Window>() { if let Some(window) =
self.as_any().downcast_ref::<crate::platform_impl::x11::window::Window>()
{
return window.request_activation_token(); return window.request_activation_token();
} }
@@ -108,6 +111,6 @@ pub fn reset_activation_token_env() {
/// ///
/// This could be used before running daemon processes. /// This could be used before running daemon processes.
pub fn set_activation_token_env(token: ActivationToken) { pub fn set_activation_token_env(token: ActivationToken) {
env::set_var(X11_VAR, &token.token); env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token.token); env::set_var(WAYLAND_VAR, token._token);
} }

View File

@@ -13,12 +13,8 @@
//! * `wayland-csd-adwaita` (default). //! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`. //! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`. //! * `wayland-csd-adwaita-notitle`.
use std::ffi::c_void;
use std::ptr::NonNull;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::platform_impl::wayland::Window; use crate::monitor::MonitorHandle;
pub use crate::window::Theme; pub use crate::window::Theme;
use crate::window::{Window as CoreWindow, WindowAttributes}; use crate::window::{Window as CoreWindow, WindowAttributes};
@@ -31,7 +27,7 @@ pub trait ActiveEventLoopExtWayland {
impl ActiveEventLoopExtWayland for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtWayland for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn is_wayland(&self) -> bool { fn is_wayland(&self) -> bool {
self.cast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some() self.as_any().downcast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some()
} }
} }
@@ -77,17 +73,9 @@ impl EventLoopBuilderExtWayland for EventLoopBuilder {
/// Additional methods on [`Window`] that are specific to Wayland. /// Additional methods on [`Window`] that are specific to Wayland.
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
pub trait WindowExtWayland { pub trait WindowExtWayland {}
/// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window.
fn xdg_toplevel(&self) -> Option<NonNull<c_void>>;
}
impl WindowExtWayland for dyn CoreWindow + '_ { impl WindowExtWayland for dyn CoreWindow + '_ {}
#[inline]
fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
self.cast_ref::<Window>()?.xdg_toplevel()
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland. /// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland { pub trait WindowAttributesExtWayland {
@@ -109,3 +97,16 @@ impl WindowAttributesExtWayland for WindowAttributes {
self self
} }
} }
/// Additional methods on `MonitorHandle` that are specific to Wayland.
pub trait MonitorHandleExtWayland {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtWayland for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -46,8 +46,8 @@ use std::error::Error;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -58,8 +58,8 @@ use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource; use crate::cursor::CustomCursorSource;
use crate::error::NotSupportedError; use crate::error::NotSupportedError;
use crate::event_loop::{ActiveEventLoop, EventLoop}; use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::monitor::MonitorHandleProvider; use crate::monitor::MonitorHandle;
use crate::platform_impl::MonitorHandle as WebMonitorHandle; use crate::platform_impl::PlatformCustomCursorSource;
#[cfg(web_platform)] #[cfg(web_platform)]
use crate::platform_impl::{ use crate::platform_impl::{
CustomCursorFuture as PlatformCustomCursorFuture, CustomCursorFuture as PlatformCustomCursorFuture,
@@ -105,23 +105,29 @@ pub trait WindowExtWeb {
impl WindowExtWeb for dyn Window + '_ { impl WindowExtWeb for dyn Window + '_ {
#[inline] #[inline]
fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> { fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> {
self.cast_ref::<crate::platform_impl::Window>().expect("non Web window on Web").canvas() self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.canvas()
} }
fn prevent_default(&self) -> bool { fn prevent_default(&self) -> bool {
self.cast_ref::<crate::platform_impl::Window>() self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web") .expect("non Web window on Web")
.prevent_default() .prevent_default()
} }
fn set_prevent_default(&self, prevent_default: bool) { fn set_prevent_default(&self, prevent_default: bool) {
self.cast_ref::<crate::platform_impl::Window>() self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web") .expect("non Web window on Web")
.set_prevent_default(prevent_default) .set_prevent_default(prevent_default)
} }
fn is_cursor_lock_raw(&self) -> bool { fn is_cursor_lock_raw(&self) -> bool {
self.cast_ref::<crate::platform_impl::Window>() self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web") .expect("non Web window on Web")
.is_cursor_lock_raw() .is_cursor_lock_raw()
} }
@@ -247,18 +253,12 @@ pub trait EventLoopExtWeb {
/// ///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New /// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead. /// [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture; fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information. /// Returns whether the user has given permission to access detailed monitor information.
/// ///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after /// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead. /// permission is granted. New [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture; fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture;
} }
@@ -348,16 +348,12 @@ pub trait ActiveEventLoopExtWeb {
/// ///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New /// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead. /// [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture; fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information. /// Returns whether the user has given permission to access detailed monitor information.
/// ///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after /// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead. /// permission is granted. New [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn has_detailed_monitor_permission(&self) -> bool; fn has_detailed_monitor_permission(&self) -> bool;
} }
@@ -365,7 +361,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture { fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.create_custom_cursor_async(source) event_loop.create_custom_cursor_async(source)
} }
@@ -373,7 +370,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) { fn set_poll_strategy(&self, strategy: PollStrategy) {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.set_poll_strategy(strategy); event_loop.set_poll_strategy(strategy);
} }
@@ -381,7 +379,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn poll_strategy(&self) -> PollStrategy { fn poll_strategy(&self) -> PollStrategy {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.poll_strategy() event_loop.poll_strategy()
} }
@@ -389,7 +388,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.set_wait_until_strategy(strategy); event_loop.set_wait_until_strategy(strategy);
} }
@@ -397,7 +397,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn wait_until_strategy(&self) -> WaitUntilStrategy { fn wait_until_strategy(&self) -> WaitUntilStrategy {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.wait_until_strategy() event_loop.wait_until_strategy()
} }
@@ -405,7 +406,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn is_cursor_lock_raw(&self) -> bool { fn is_cursor_lock_raw(&self) -> bool {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.is_cursor_lock_raw() event_loop.is_cursor_lock_raw()
} }
@@ -413,7 +415,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> { fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.has_multiple_screens() event_loop.has_multiple_screens()
} }
@@ -421,7 +424,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture { fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
MonitorPermissionFuture(event_loop.request_detailed_monitor_permission()) MonitorPermissionFuture(event_loop.request_detailed_monitor_permission())
} }
@@ -429,7 +433,8 @@ impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn has_detailed_monitor_permission(&self) -> bool { fn has_detailed_monitor_permission(&self) -> bool {
let event_loop = self let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>() .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web"); .expect("non Web event loop on Web");
event_loop.has_detailed_monitor_permission() event_loop.has_detailed_monitor_permission()
} }
@@ -486,6 +491,73 @@ pub enum WaitUntilStrategy {
Worker, Worker,
} }
pub trait CustomCursorExtWeb {
/// Returns if this cursor is an animation.
fn is_animation(&self) -> bool;
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource;
/// Crates a new animated cursor from multiple [`CustomCursor`]s.
/// Supplied `cursors` can't be empty or other animations.
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation>;
}
impl CustomCursorExtWeb for CustomCursor {
fn is_animation(&self) -> bool {
self.inner.animation
}
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
CustomCursorSource { inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y } }
}
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation> {
if cursors.is_empty() {
return Err(BadAnimation::Empty);
}
if cursors.iter().any(CustomCursor::is_animation) {
return Err(BadAnimation::Animation);
}
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::Animation { duration, cursors },
})
}
}
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum BadAnimation {
/// Produced when no cursors were supplied.
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
}
}
}
impl Error for BadAnimation {}
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
struct PlatformCustomCursorFuture; struct PlatformCustomCursorFuture;
@@ -496,7 +568,7 @@ impl Future for CustomCursorFuture {
type Output = Result<CustomCursor, CustomCursorError>; type Output = Result<CustomCursor, CustomCursorError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor(Arc::new(cursor))) Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor { inner: cursor })
} }
} }
@@ -578,8 +650,6 @@ impl Future for HasMonitorPermissionFuture {
} }
/// Additional methods on [`MonitorHandle`] that are specific to the Web. /// Additional methods on [`MonitorHandle`] that are specific to the Web.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
pub trait MonitorHandleExtWeb { pub trait MonitorHandleExtWeb {
/// Returns whether the screen is internal to the device or external. /// Returns whether the screen is internal to the device or external.
/// ///
@@ -607,31 +677,28 @@ pub trait MonitorHandleExtWeb {
/// specific monitor. /// specific monitor.
/// ///
/// See [`ActiveEventLoopExtWeb::request_detailed_monitor_permission()`]. /// See [`ActiveEventLoopExtWeb::request_detailed_monitor_permission()`].
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn is_detailed(&self) -> bool; fn is_detailed(&self) -> bool;
} }
impl MonitorHandleExtWeb for dyn MonitorHandleProvider + '_ { impl MonitorHandleExtWeb for MonitorHandle {
fn is_internal(&self) -> Option<bool> { fn is_internal(&self) -> Option<bool> {
self.cast_ref::<WebMonitorHandle>().unwrap().is_internal() self.inner.is_internal()
} }
fn orientation(&self) -> OrientationData { fn orientation(&self) -> OrientationData {
self.cast_ref::<WebMonitorHandle>().unwrap().orientation() self.inner.orientation()
} }
fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture { fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture {
let future = self.cast_ref::<WebMonitorHandle>().unwrap().request_lock(orientation_lock); OrientationLockFuture(self.inner.request_lock(orientation_lock))
OrientationLockFuture(future)
} }
fn unlock(&self) -> Result<(), OrientationLockError> { fn unlock(&self) -> Result<(), OrientationLockError> {
self.cast_ref::<WebMonitorHandle>().unwrap().unlock() self.inner.unlock()
} }
fn is_detailed(&self) -> bool { fn is_detailed(&self) -> bool {
self.cast_ref::<WebMonitorHandle>().unwrap().is_detailed() self.inner.is_detailed()
} }
} }

View File

@@ -6,7 +6,6 @@ use std::borrow::Borrow;
use std::ffi::c_void; use std::ffi::c_void;
use std::ops::Deref; use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -16,16 +15,15 @@ use windows_sys::Win32::Foundation::HANDLE;
use crate::dpi::PhysicalSize; use crate::dpi::PhysicalSize;
use crate::event::DeviceId; use crate::event::DeviceId;
use crate::event_loop::EventLoopBuilder; use crate::event_loop::EventLoopBuilder;
use crate::icon::BadIcon; use crate::monitor::MonitorHandle;
use crate::platform_impl::RaiiIcon; use crate::window::{BadIcon, Icon, Window, WindowAttributes};
use crate::window::{Icon, Window, WindowAttributes};
/// Window Handle type used by Win32 API /// Window Handle type used by Win32 API
pub type HWND = *mut c_void; pub type HWND = isize;
/// Menu Handle type used by Win32 API /// Menu Handle type used by Win32 API
pub type HMENU = *mut c_void; pub type HMENU = isize;
/// Monitor Handle type used by Win32 API /// Monitor Handle type used by Win32 API
pub type HMONITOR = *mut c_void; pub type HMONITOR = isize;
/// Describes a system-drawn backdrop material of a window. /// Describes a system-drawn backdrop material of a window.
/// ///
@@ -346,37 +344,37 @@ pub trait WindowExtWindows {
impl WindowExtWindows for dyn Window + '_ { impl WindowExtWindows for dyn Window + '_ {
#[inline] #[inline]
fn set_enable(&self, enabled: bool) { fn set_enable(&self, enabled: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_enable(enabled) window.set_enable(enabled)
} }
#[inline] #[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) { fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_taskbar_icon(taskbar_icon) window.set_taskbar_icon(taskbar_icon)
} }
#[inline] #[inline]
fn set_skip_taskbar(&self, skip: bool) { fn set_skip_taskbar(&self, skip: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_skip_taskbar(skip) window.set_skip_taskbar(skip)
} }
#[inline] #[inline]
fn set_undecorated_shadow(&self, shadow: bool) { fn set_undecorated_shadow(&self, shadow: bool) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_undecorated_shadow(shadow) window.set_undecorated_shadow(shadow)
} }
#[inline] #[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) { fn set_system_backdrop(&self, backdrop_type: BackdropType) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_system_backdrop(backdrop_type) window.set_system_backdrop(backdrop_type)
} }
#[inline] #[inline]
fn set_border_color(&self, color: Option<Color>) { fn set_border_color(&self, color: Option<Color>) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_border_color(color.unwrap_or(Color::NONE)) window.set_border_color(color.unwrap_or(Color::NONE))
} }
@@ -385,26 +383,26 @@ impl WindowExtWindows for dyn Window + '_ {
// The windows docs don't mention NONE as a valid options but it works in practice and is // The windows docs don't mention NONE as a valid options but it works in practice and is
// useful to circumvent the Windows option "Show accent color on title bars and // useful to circumvent the Windows option "Show accent color on title bars and
// window borders" // window borders"
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_title_background_color(color.unwrap_or(Color::NONE)) window.set_title_background_color(color.unwrap_or(Color::NONE))
} }
#[inline] #[inline]
fn set_title_text_color(&self, color: Color) { fn set_title_text_color(&self, color: Color) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_title_text_color(color) window.set_title_text_color(color)
} }
#[inline] #[inline]
fn set_corner_preference(&self, preference: CornerPreference) { fn set_corner_preference(&self, preference: CornerPreference) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_corner_preference(preference) window.set_corner_preference(preference)
} }
unsafe fn window_handle_any_thread( unsafe fn window_handle_any_thread(
&self, &self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> { ) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
unsafe { unsafe {
let handle = window.rwh_06_no_thread_check()?; let handle = window.rwh_06_no_thread_check()?;
@@ -620,6 +618,27 @@ impl WindowAttributesExtWindows for WindowAttributes {
} }
} }
/// Additional methods on `MonitorHandle` that are specific to Windows.
pub trait MonitorHandleExtWindows {
/// Returns the name of the monitor adapter specific to the Win32 API.
fn native_id(&self) -> String;
/// Returns the handle of the monitor - `HMONITOR`.
fn hmonitor(&self) -> HMONITOR;
}
impl MonitorHandleExtWindows for MonitorHandle {
#[inline]
fn native_id(&self) -> String {
self.inner.native_identifier()
}
#[inline]
fn hmonitor(&self) -> HMONITOR {
self.inner.hmonitor()
}
}
/// Additional methods on `DeviceId` that are specific to Windows. /// Additional methods on `DeviceId` that are specific to Windows.
pub trait DeviceIdExtWindows { pub trait DeviceIdExtWindows {
/// Returns an identifier that persistently refers to this specific device. /// Returns an identifier that persistently refers to this specific device.
@@ -640,24 +659,8 @@ impl DeviceIdExtWindows for DeviceId {
} }
} }
/// Windows specific `Icon`. /// Additional methods on `Icon` that are specific to Windows.
/// pub trait IconExtWindows: Sized {
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
///
/// The `ICON` resource definition statement use the following syntax:
/// ```rc
/// nameID ICON filename
/// ```
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
/// `filename` is the name of the file that contains the resource.
///
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct WinIcon {
pub(crate) inner: Arc<RaiiIcon>,
}
impl WinIcon {
/// Create an icon from a file path. /// Create an icon from a file path.
/// ///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default /// Specify `size` to load a specific icon size from the file, or `None` to load the default
@@ -665,88 +668,30 @@ impl WinIcon {
/// ///
/// In cases where the specified size does not exist in the file, Windows may perform scaling /// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size. /// to get an icon of the desired size.
pub fn from_path<P: AsRef<Path>>( fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
-> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
fn from_path<P: AsRef<Path>>(
path: P, path: P,
size: Option<PhysicalSize<u32>>, size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> { ) -> Result<Self, BadIcon> {
Self::from_path_impl(path, size) let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon })
} }
/// Create an icon from a resource embedded in this executable or library by its ordinal id. fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
/// let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an Ok(Icon { inner: win_icon })
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
///
/// [`from_resource_name`]: Self::from_resource_name
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
pub fn from_resource(
resource_id: u16,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
Self::from_resource_impl(resource_id, size)
}
/// Create an icon from a resource embedded in this executable or library by its name.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
///
/// # Notes
///
/// Consider the following resource definition statements:
/// ```rc
/// app ICON "app.ico"
/// 1 ICON "a.ico"
/// 0027 ICON "custom.ico"
/// 0 ICON "alt.ico"
/// ```
///
/// Due to some internal implementation details of the resource embedding/loading process on
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
/// [`from_resource`]:
///
/// [`from_resource`]: Self::from_resource
///
/// ```rust,no_run
/// use winit::platform::windows::WinIcon;
///
/// assert!(WinIcon::from_resource_name("app", None).is_ok());
/// assert!(WinIcon::from_resource(1, None).is_ok());
/// assert!(WinIcon::from_resource(27, None).is_ok());
/// assert!(WinIcon::from_resource_name("27", None).is_err());
/// assert!(WinIcon::from_resource_name("0027", None).is_err());
/// ```
///
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
/// name:
///
/// [`from_resource`]: IconExtWindows::from_resource
///
/// ```rust,no_run
/// # use winit::platform::windows::WinIcon;
/// # use winit::window::Icon;
/// assert!(WinIcon::from_resource_name("0", None).is_ok());
/// assert!(WinIcon::from_resource(0, None).is_err());
/// ```
pub fn from_resource_name(
resource_name: &str,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
Self::from_resource_name_impl(resource_name, size)
}
}
impl From<WinIcon> for Icon {
fn from(value: WinIcon) -> Self {
Self(Arc::new(value))
} }
} }

View File

@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use crate::dpi::Size; use crate::dpi::Size;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window as CoreWindow, WindowAttributes}; use crate::window::{Window as CoreWindow, WindowAttributes};
/// X window type. Maps directly to /// X window type. Maps directly to
@@ -79,7 +80,7 @@ pub type XWindow = u32;
#[inline] #[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) { pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook. // Append new hook.
crate::platform_impl::x11::XLIB_ERROR_HOOKS.lock().unwrap().push(hook); crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
} }
/// Additional methods on [`ActiveEventLoop`] that are specific to X11. /// Additional methods on [`ActiveEventLoop`] that are specific to X11.
@@ -91,7 +92,7 @@ pub trait ActiveEventLoopExtX11 {
impl ActiveEventLoopExtX11 for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtX11 for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn is_x11(&self) -> bool { fn is_x11(&self) -> bool {
self.cast_ref::<crate::platform_impl::x11::ActiveEventLoop>().is_some() self.as_any().downcast_ref::<crate::platform_impl::x11::ActiveEventLoop>().is_some()
} }
} }
@@ -239,3 +240,16 @@ impl WindowAttributesExtX11 for WindowAttributes {
self self
} }
} }
/// Additional methods on `MonitorHandle` that are specific to X11.
pub trait MonitorHandleExtX11 {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtX11 for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -143,8 +143,8 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::AltLeft => KeyCode::AltLeft, Keycode::AltLeft => KeyCode::AltLeft,
Keycode::AltRight => KeyCode::AltRight, Keycode::AltRight => KeyCode::AltRight,
Keycode::MetaLeft => KeyCode::MetaLeft, Keycode::MetaLeft => KeyCode::SuperLeft,
Keycode::MetaRight => KeyCode::MetaRight, Keycode::MetaRight => KeyCode::SuperRight,
Keycode::LeftBracket => KeyCode::BracketLeft, Keycode::LeftBracket => KeyCode::BracketLeft,
Keycode::RightBracket => KeyCode::BracketRight, Keycode::RightBracket => KeyCode::BracketRight,
@@ -309,7 +309,7 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
ShiftLeft => Key::Named(NamedKey::Shift), ShiftLeft => Key::Named(NamedKey::Shift),
ShiftRight => Key::Named(NamedKey::Shift), ShiftRight => Key::Named(NamedKey::Shift),
Tab => Key::Named(NamedKey::Tab), Tab => Key::Named(NamedKey::Tab),
Space => Key::Character(" ".into()), Space => Key::Named(NamedKey::Space),
Sym => Key::Named(NamedKey::Symbol), Sym => Key::Named(NamedKey::Symbol),
Explorer => Key::Named(NamedKey::LaunchWebBrowser), Explorer => Key::Named(NamedKey::LaunchWebBrowser),
Envelope => Key::Named(NamedKey::LaunchMail), Envelope => Key::Named(NamedKey::LaunchMail),
@@ -340,8 +340,8 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
CtrlRight => Key::Named(NamedKey::Control), CtrlRight => Key::Named(NamedKey::Control),
CapsLock => Key::Named(NamedKey::CapsLock), CapsLock => Key::Named(NamedKey::CapsLock),
ScrollLock => Key::Named(NamedKey::ScrollLock), ScrollLock => Key::Named(NamedKey::ScrollLock),
MetaLeft => Key::Named(NamedKey::Meta), MetaLeft => Key::Named(NamedKey::Super),
MetaRight => Key::Named(NamedKey::Meta), MetaRight => Key::Named(NamedKey::Super),
Function => Key::Named(NamedKey::Fn), Function => Key::Named(NamedKey::Fn),
Sysrq => Key::Named(NamedKey::PrintScreen), Sysrq => Key::Named(NamedKey::PrintScreen),
Break => Key::Named(NamedKey::Pause), Break => Key::Named(NamedKey::Pause),

View File

@@ -1,5 +1,6 @@
use std::cell::Cell; use std::cell::Cell;
use std::hash::Hash; use std::hash::Hash;
use std::num::{NonZeroU16, NonZeroU32};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -20,15 +21,21 @@ use crate::event_loop::{
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle, OwnedDisplayHandle as CoreOwnedDisplayHandle,
}; };
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::pump_events::PumpStatus; use crate::platform::pump_events::PumpStatus;
use crate::window::{ use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme, self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose,
Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
}; };
mod keycodes; mod keycodes;
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true); static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
/// Returns the minimum `Option<Duration>`, taking into account that `None` /// Returns the minimum `Option<Duration>`, taking into account that `None`
@@ -38,7 +45,7 @@ fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))) a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
} }
#[derive(Clone, Debug)] #[derive(Clone)]
struct SharedFlagSetter { struct SharedFlagSetter {
flag: Arc<AtomicBool>, flag: Arc<AtomicBool>,
} }
@@ -48,7 +55,6 @@ impl SharedFlagSetter {
} }
} }
#[derive(Debug)]
struct SharedFlag { struct SharedFlag {
flag: Arc<AtomicBool>, flag: Arc<AtomicBool>,
} }
@@ -77,12 +83,6 @@ pub struct RedrawRequester {
waker: AndroidAppWaker, waker: AndroidAppWaker,
} }
impl fmt::Debug for RedrawRequester {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RedrawRequester").field("flag", &self.flag).finish_non_exhaustive()
}
}
impl RedrawRequester { impl RedrawRequester {
fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self { fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self {
RedrawRequester { flag: flag.setter(), waker } RedrawRequester { flag: flag.setter(), waker }
@@ -97,7 +97,9 @@ impl RedrawRequester {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop { pub struct EventLoop {
pub(crate) android_app: AndroidApp, pub(crate) android_app: AndroidApp,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
@@ -477,8 +479,7 @@ impl EventLoop {
location: keycodes::to_location(keycode), location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0, repeat: key.repeat_count() > 0,
text: None, text: None,
text_with_all_modifiers: None, platform_specific: KeyEventExtra {},
key_without_modifiers: keycodes::to_logical(key_char, keycode),
}, },
is_synthetic: false, is_synthetic: false,
}; };
@@ -545,6 +546,8 @@ impl EventLoop {
if self.exiting() { if self.exiting() {
self.loop_running = false; self.loop_running = false;
app.exiting(&self.window_target);
PumpStatus::Exit(0) PumpStatus::Exit(0)
} else { } else {
PumpStatus::Continue PumpStatus::Continue
@@ -639,12 +642,6 @@ pub struct EventLoopProxy {
waker: AndroidAppWaker, waker: AndroidAppWaker,
} }
impl fmt::Debug for EventLoopProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopProxy").field("wake_up", &self.wake_up).finish_non_exhaustive()
}
}
impl EventLoopProxy { impl EventLoopProxy {
fn new(waker: AndroidAppWaker) -> Self { fn new(waker: AndroidAppWaker) -> Self {
Self { wake_up: AtomicBool::new(false), waker } Self { wake_up: AtomicBool::new(false), waker }
@@ -658,7 +655,6 @@ impl EventLoopProxyProvider for EventLoopProxy {
} }
} }
#[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
pub(crate) app: AndroidApp, pub(crate) app: AndroidApp,
control_flow: Cell<ControlFlow>, control_flow: Cell<ControlFlow>,
@@ -692,11 +688,11 @@ impl RootActiveEventLoop for ActiveEventLoop {
Err(NotSupportedError::new("create_custom_cursor is not supported").into()) Err(NotSupportedError::new("create_custom_cursor is not supported").into())
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(std::iter::empty()) Box::new(std::iter::empty())
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None None
} }
@@ -751,7 +747,6 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes; pub struct PlatformSpecificWindowAttributes;
#[derive(Debug)]
pub(crate) struct Window { pub(crate) struct Window {
app: AndroidApp, app: AndroidApp,
redraw_requester: RedrawRequester, redraw_requester: RedrawRequester,
@@ -816,15 +811,15 @@ impl CoreWindow for Window {
GLOBAL_WINDOW GLOBAL_WINDOW
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None None
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(std::iter::empty()) Box::new(std::iter::empty())
} }
fn current_monitor(&self) -> Option<CoreMonitorHandle> { fn current_monitor(&self) -> Option<RootMonitorHandle> {
None None
} }
@@ -1010,6 +1005,52 @@ impl Display for OsError {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MonitorHandle;
impl MonitorHandle {
pub fn name(&self) -> Option<String> {
unreachable!()
}
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
unreachable!()
}
pub fn scale_factor(&self) -> f64 {
unreachable!()
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
unreachable!()
}
pub fn video_modes(&self) -> std::iter::Empty<VideoModeHandle> {
unreachable!()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoModeHandle;
impl VideoModeHandle {
pub fn size(&self) -> PhysicalSize<u32> {
unreachable!()
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
unreachable!()
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
unreachable!()
}
pub fn monitor(&self) -> MonitorHandle {
unreachable!()
}
}
fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> { fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window() { if let Some(native_window) = app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _) PhysicalSize::new(native_window.width() as _, native_window.height() as _)

View File

@@ -1,101 +1,51 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use dispatch2::MainThreadBound; use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2::runtime::{Imp, Sel}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2::sel; use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker;
use super::app_state::AppState; use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState}; use crate::event::{DeviceEvent, ElementState};
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent); declare_class!(
pub(super) struct WinitApplication;
static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = { unsafe impl ClassType for WinitApplication {
// SAFETY: Creating in a `const` context, where there is no concept of the main thread. #[inherits(NSResponder, NSObject)]
MainThreadBound::new(Cell::new(None), unsafe { MainThreadMarker::new_unchecked() }) type Super = NSApplication;
}; type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) { impl DeclaredClass for WinitApplication {}
let mtm = MainThreadMarker::from(app);
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. unsafe impl WinitApplication {
// Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196) // Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
// // Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
// For posterity, there are some undocumented event types #[method(sendEvent:)]
// (https://github.com/servo/cocoa-rs/issues/155) fn send_event(&self, event: &NSEvent) {
// but that doesn't really matter here. // For posterity, there are some undocumented event types
let event_type = unsafe { event.r#type() }; // (https://github.com/servo/cocoa-rs/issues/155)
let modifier_flags = unsafe { event.modifierFlags() }; // but that doesn't really matter here.
if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::Command) { let event_type = unsafe { event.r#type() };
if let Some(key_window) = app.keyWindow() { let modifier_flags = unsafe { event.modifierFlags() };
key_window.sendEvent(event); if event_type == NSEventType::KeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
{
if let Some(key_window) = self.keyWindow() {
key_window.sendEvent(event);
}
} else {
let app_state = AppState::get(MainThreadMarker::from(self));
maybe_dispatch_device_event(&app_state, event);
unsafe { msg_send![super(self), sendEvent: event] }
}
} }
return;
} }
);
// Events are generally scoped to the window level, so the best way
// to get device events is to listen for them on NSApplication.
let app_state = AppState::get(mtm);
maybe_dispatch_device_event(&app_state, event);
let original = ORIGINAL.get(mtm).get().expect("no existing sendEvent: handler set");
original(app, sel, event)
}
/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class.
///
/// The previous implementation created a subclass of [`NSApplication`], however we would like to
/// give the user full control over their `NSApplication`, so we override the method here using
/// method swizzling instead.
///
/// This _should_ also allow two versions of Winit to exist in the same application.
///
/// See the following links for more info on method swizzling:
/// - <https://nshipster.com/method-swizzling/>
/// - <https://spin.atomicobject.com/method-swizzling-objective-c/>
/// - <https://web.archive.org/web/20130308110627/http://cocoadev.com/wiki/MethodSwizzling>
///
/// NOTE: This function assumes that the passed in application object is the one returned from
/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object.
/// For testing though, we allow it to be a different object.
pub(crate) fn override_send_event(global_app: &NSApplication) {
let mtm = MainThreadMarker::from(global_app);
let class = global_app.class();
let method =
class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method");
// SAFETY: Converting our `sendEvent:` implementation to an IMP.
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything.
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
if overridden == method.implementation() {
return;
}
// SAFETY: Our implementation has:
// 1. The same signature as `sendEvent:`.
// 2. Does not impose extra safety requirements on callers.
let original = unsafe { method.set_implementation(overridden) };
// SAFETY: This is the actual signature of `sendEvent:`.
let original = unsafe { mem::transmute::<Imp, SendEvent>(original) };
// NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be
// a (checked) race-condition, since one could call `sendEvent:` before the original had been
// stored here.
//
// It is only usable from the main thread, however, so we're good!
ORIGINAL.get(mtm).set(Some(original));
}
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) { fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let event_type = unsafe { event.r#type() }; let event_type = unsafe { event.r#type() };
@@ -137,52 +87,3 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
_ => (), _ => (),
} }
} }
#[cfg(test)]
mod tests {
use objc2::rc::Retained;
use objc2::{define_class, msg_send, ClassType};
use objc2_app_kit::NSResponder;
use objc2_foundation::NSObject;
use super::*;
#[test]
fn test_override() {
// FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?)
let Some(mtm) = MainThreadMarker::new() else { return };
// Create a new application, without making it the shared application.
let app = unsafe { NSApplication::new(mtm) };
override_send_event(&app);
// Test calling twice works.
override_send_event(&app);
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
// unsafe {
// let event = super::super::event::dummy_event().unwrap();
// app.sendEvent(&event)
// }
}
#[test]
fn test_custom_class() {
let Some(_mtm) = MainThreadMarker::new() else { return };
define_class!(
#[unsafe(super(NSApplication, NSResponder, NSObject))]
#[name = "TestApplication"]
pub(super) struct TestApplication;
impl TestApplication {
#[unsafe(method(sendEvent:))]
fn send_event(&self, _event: &NSEvent) {
todo!()
}
}
);
let app: Retained<TestApplication> = unsafe { msg_send![TestApplication::class(), new] };
override_send_event(&app);
}
}

View File

@@ -1,17 +1,15 @@
use std::cell::{Cell, OnceCell, RefCell}; use std::cell::{Cell, OnceCell, RefCell};
use std::mem; use std::mem;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::sync::atomic::Ordering as AtomicOrdering;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification; use objc2_foundation::{MainThreadMarker, NSNotification};
use super::super::event_handler::EventHandler; use super::super::event_handler::EventHandler;
use super::super::event_loop_proxy::EventLoopProxy; use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::menu; use super::menu;
use super::observer::{EventLoopWaker, RunLoop}; use super::observer::{EventLoopWaker, RunLoop};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
@@ -47,10 +45,22 @@ pub(super) struct AppState {
// as such should be careful to not add fields that, in turn, strongly reference those. // as such should be careful to not add fields that, in turn, strongly reference those.
} }
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the // TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the
// main thread. // main thread.
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> = static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() }); StaticMainThreadBound(OnceCell::new());
impl AppState { impl AppState {
pub(super) fn setup_global( pub(super) fn setup_global(
@@ -59,17 +69,13 @@ impl AppState {
default_menu: bool, default_menu: bool,
activate_ignoring_other_apps: bool, activate_ignoring_other_apps: bool,
) -> Rc<Self> { ) -> Rc<Self> {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || { let this = Rc::new(AppState {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));
let this = Rc::new(Self {
mtm, mtm,
activation_policy, activation_policy,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
default_menu, default_menu,
activate_ignoring_other_apps, activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm), run_loop: RunLoop::main(mtm),
event_loop_proxy,
event_handler: EventHandler::new(), event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false), stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false), stop_before_wait: Cell::new(false),
@@ -156,9 +162,7 @@ impl AppState {
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) { pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification"); trace_scope!("NSApplicationWillTerminateNotification");
let app = NSApplication::sharedApplication(self.mtm); // TODO: Notify every window that it will be destroyed, like done in iOS?
notify_windows_of_exit(&app);
self.event_handler.terminate();
self.internal_exit(); self.internal_exit();
} }
@@ -166,10 +170,10 @@ impl AppState {
/// of the given closure. /// of the given closure.
pub fn set_event_handler<R>( pub fn set_event_handler<R>(
&self, &self,
handler: impl ApplicationHandler, handler: &mut dyn ApplicationHandler,
closure: impl FnOnce() -> R, closure: impl FnOnce() -> R,
) -> R { ) -> R {
self.event_handler.set(Box::new(handler), closure) self.event_handler.set(handler, closure)
} }
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> { pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
@@ -204,6 +208,10 @@ impl AppState {
/// NOTE: that if the `NSApplication` has been launched then that state is preserved, /// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run. /// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(self: &Rc<Self>) { pub fn internal_exit(self: &Rc<Self>) {
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.set_is_running(false); self.set_is_running(false);
self.set_stop_on_redraw(false); self.set_stop_on_redraw(false);
self.set_stop_before_wait(false); self.set_stop_before_wait(false);
@@ -353,6 +361,10 @@ impl AppState {
return; return;
} }
if self.event_loop_proxy.wake_up.swap(false, AtomicOrdering::Relaxed) {
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut()); let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw { for window_id in redraw {
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {
@@ -366,7 +378,6 @@ impl AppState {
if self.exiting() { if self.exiting() {
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app); stop_app_immediately(&app);
notify_windows_of_exit(&app);
} }
if self.stop_before_wait.get() { if self.stop_before_wait.get() {

View File

@@ -4,40 +4,28 @@ use std::sync::OnceLock;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{available, msg_send, sel, AllocAnyThread, ClassType}; use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{ use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
NSString,
}; };
use crate::cursor::{CursorImage, CustomCursorProvider, CustomCursorSource}; use crate::cursor::{CursorImage, OnlyCursorImageSource};
use crate::error::{NotSupportedError, RequestError}; use crate::error::RequestError;
use crate::window::CursorIcon; use crate::window::CursorIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomCursor(pub(crate) Retained<NSCursor>); pub struct CustomCursor(pub(crate) Retained<NSCursor>);
impl CustomCursorProvider for CustomCursor {
fn is_animated(&self) -> bool {
false
}
}
// SAFETY: NSCursor is immutable and thread-safe // SAFETY: NSCursor is immutable and thread-safe
// TODO(madsmtm): Put this logic in objc2-app-kit itself // TODO(madsmtm): Put this logic in objc2-app-kit itself
unsafe impl Send for CustomCursor {} unsafe impl Send for CustomCursor {}
unsafe impl Sync for CustomCursor {} unsafe impl Sync for CustomCursor {}
impl CustomCursor { impl CustomCursor {
pub(crate) fn new(cursor: CustomCursorSource) -> Result<CustomCursor, RequestError> { pub(crate) fn new(cursor: OnlyCursorImageSource) -> Result<CustomCursor, RequestError> {
let cursor = match cursor { cursor_from_image(&cursor.0).map(Self)
CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
cursor_from_image(&cursor).map(Self)
} }
} }
@@ -79,8 +67,8 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> { unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class(); let cls = NSCursor::class();
if unsafe { msg_send![cls, respondsToSelector: sel] } { if cls.responds_to(sel) {
let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] }; let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor) Some(cursor)
} else { } else {
tracing::warn!("cursor `{sel}` appears to be invalid"); tracing::warn!("cursor `{sel}` appears to be invalid");
@@ -142,21 +130,25 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
// TODO: Handle PLists better // TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist")); let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe { let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send![ msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(), <NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path, dictionaryWithContentsOfFile: &*info_path,
] ]
}; };
let mut x = 0.0; let mut x = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hotx")) { if let Some(n) = info.get(&*ns_string!("hotx")) {
if let Ok(n) = n.downcast::<NSNumber>() { if n.is_kind_of::<NSNumber>() {
x = n.as_cgfloat(); let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
x = unsafe { &*ptr }.as_cgfloat()
} }
} }
let mut y = 0.0; let mut y = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hoty")) { if let Some(n) = info.get(&*ns_string!("hotx")) {
if let Ok(n) = n.downcast::<NSNumber>() { if n.is_kind_of::<NSNumber>() {
y = n.as_cgfloat(); let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
y = unsafe { &*ptr }.as_cgfloat()
} }
} }
@@ -196,7 +188,6 @@ pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone() CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
} }
#[allow(deprecated)]
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> { pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
match icon { match icon {
CursorIcon::Default => default_cursor(), CursorIcon::Default => default_cursor(),
@@ -217,9 +208,7 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(), CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(), CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
CursorIcon::Help => _helpCursor(), CursorIcon::Help => _helpCursor(),
CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() },
CursorIcon::ZoomIn => _zoomInCursor(), CursorIcon::ZoomIn => _zoomInCursor(),
CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() },
CursorIcon::ZoomOut => _zoomOutCursor(), CursorIcon::ZoomOut => _zoomOutCursor(),
CursorIcon::NeResize => _windowResizeNorthEastCursor(), CursorIcon::NeResize => _windowResizeNorthEastCursor(),
CursorIcon::NwResize => _windowResizeNorthWestCursor(), CursorIcon::NwResize => _windowResizeNorthWestCursor(),

View File

@@ -1,10 +1,10 @@
use std::ptr::NonNull; use std::ffi::c_void;
use dispatch2::run_on_main; use core_foundation::base::CFRelease;
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType}; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_core_foundation::{CFData, CFRetained}; use objc2_foundation::{run_on_main, NSPoint};
use objc2_foundation::NSPoint;
use smol_str::SmolStr; use smol_str::SmolStr;
use super::ffi; use super::ffi;
@@ -14,29 +14,37 @@ use crate::keyboard::{
PhysicalKey, PhysicalKey,
}; };
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key,
}
/// Ignores ALL modifiers. /// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key { pub fn get_modifierless_char(scancode: u16) -> Key {
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else { let mut string = [0; 16];
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); let input_source;
return Key::Unidentified(NativeKey::MacOS(scancode)); let layout;
}; unsafe {
let input_source = unsafe { CFRetained::from_raw(ptr) }; input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
if input_source.is_null() {
let layout_data = unsafe { tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData) return Key::Unidentified(NativeKey::MacOS(scancode));
}; }
let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else { let layout_data =
tracing::error!("`TISGetInputSourceProperty` returned null ptr"); ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
return Key::Unidentified(NativeKey::MacOS(scancode)); if layout_data.is_null() {
}; CFRelease(input_source as *mut c_void);
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
let layout = layout_data.byte_ptr().cast(); return Key::Unidentified(NativeKey::MacOS(scancode));
}
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
}
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() }); let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
let mut result_len = 0; let mut result_len = 0;
let mut dead_keys = 0; let mut dead_keys = 0;
let modifiers = 0; let modifiers = 0;
let mut string = [0; 16];
let translate_result = unsafe { let translate_result = unsafe {
ffi::UCKeyTranslate( ffi::UCKeyTranslate(
layout, layout,
@@ -51,6 +59,9 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
string.as_mut_ptr(), string.as_mut_ptr(),
) )
}; };
unsafe {
CFRelease(input_source as *mut c_void);
}
if translate_result != 0 { if translate_result != 0 {
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result); tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
return Key::Unidentified(NativeKey::MacOS(scancode)); return Key::Unidentified(NativeKey::MacOS(scancode));
@@ -112,8 +123,8 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
let key_without_modifiers = get_modifierless_char(scancode); let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = unsafe { ns_event.modifierFlags() }; let modifiers = unsafe { ns_event.modifierFlags() };
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control); let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
let has_cmd = modifiers.contains(NSEventModifierFlags::Command); let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
let logical_key = match text_with_all_modifiers.as_ref() { let logical_key = match text_with_all_modifiers.as_ref() {
// Only checking for ctrl and cmd here, not checking for alt because we DO want to // Only checking for ctrl and cmd here, not checking for alt because we DO want to
@@ -151,8 +162,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
repeat: is_repeat, repeat: is_repeat,
state, state,
text, text,
text_with_all_modifiers, platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
key_without_modifiers,
} }
} }
@@ -162,34 +172,28 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()), PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()),
}; };
// Roughly same handling as Firefox and Chromium:
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMKeyName.h
// https://chromium.googlesource.com/chromium/src.git/+/010a75a426c4a2292955a52f480e9251cacf750e/ui/events/keycodes/keyboard_code_conversion_mac.mm#100
Key::Named(match code { Key::Named(match code {
KeyCode::Enter => NamedKey::Enter, KeyCode::Enter => NamedKey::Enter,
KeyCode::Tab => NamedKey::Tab, KeyCode::Tab => NamedKey::Tab,
KeyCode::Space => return Key::Character(" ".into()), KeyCode::Space => NamedKey::Space,
KeyCode::Backspace => NamedKey::Backspace, KeyCode::Backspace => NamedKey::Backspace,
KeyCode::Escape => NamedKey::Escape, KeyCode::Escape => NamedKey::Escape,
KeyCode::MetaRight => NamedKey::Meta, KeyCode::SuperRight => NamedKey::Super,
KeyCode::MetaLeft => NamedKey::Meta, KeyCode::SuperLeft => NamedKey::Super,
KeyCode::ShiftLeft => NamedKey::Shift, KeyCode::ShiftLeft => NamedKey::Shift,
KeyCode::AltLeft => NamedKey::Alt, KeyCode::AltLeft => NamedKey::Alt,
KeyCode::ControlLeft => NamedKey::Control, KeyCode::ControlLeft => NamedKey::Control,
KeyCode::ShiftRight => NamedKey::Shift, KeyCode::ShiftRight => NamedKey::Shift,
KeyCode::AltRight => NamedKey::Alt, KeyCode::AltRight => NamedKey::Alt,
KeyCode::ControlRight => NamedKey::Control, KeyCode::ControlRight => NamedKey::Control,
KeyCode::CapsLock => NamedKey::CapsLock,
KeyCode::NumLock => NamedKey::NumLock, KeyCode::NumLock => NamedKey::NumLock,
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp, KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown, KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
KeyCode::AudioVolumeMute => NamedKey::AudioVolumeMute,
// Other numpad keys all generate text on macOS (if I understand correctly) // Other numpad keys all generate text on macOS (if I understand correctly)
KeyCode::NumpadEnter => NamedKey::Enter, KeyCode::NumpadEnter => NamedKey::Enter,
KeyCode::Fn => NamedKey::Fn,
KeyCode::F1 => NamedKey::F1, KeyCode::F1 => NamedKey::F1,
KeyCode::F2 => NamedKey::F2, KeyCode::F2 => NamedKey::F2,
KeyCode::F3 => NamedKey::F3, KeyCode::F3 => NamedKey::F3,
@@ -210,27 +214,17 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
KeyCode::F18 => NamedKey::F18, KeyCode::F18 => NamedKey::F18,
KeyCode::F19 => NamedKey::F19, KeyCode::F19 => NamedKey::F19,
KeyCode::F20 => NamedKey::F20, KeyCode::F20 => NamedKey::F20,
KeyCode::F21 => NamedKey::F21,
KeyCode::F22 => NamedKey::F22,
KeyCode::F23 => NamedKey::F23,
KeyCode::F24 => NamedKey::F24,
KeyCode::Insert => NamedKey::Insert, KeyCode::Insert => NamedKey::Insert,
KeyCode::Home => NamedKey::Home, KeyCode::Home => NamedKey::Home,
KeyCode::PageUp => NamedKey::PageUp, KeyCode::PageUp => NamedKey::PageUp,
KeyCode::Delete => NamedKey::Delete, KeyCode::Delete => NamedKey::Delete,
KeyCode::End => NamedKey::End, KeyCode::End => NamedKey::End,
KeyCode::Help => NamedKey::Help,
KeyCode::PageDown => NamedKey::PageDown, KeyCode::PageDown => NamedKey::PageDown,
KeyCode::ArrowLeft => NamedKey::ArrowLeft, KeyCode::ArrowLeft => NamedKey::ArrowLeft,
KeyCode::ArrowRight => NamedKey::ArrowRight, KeyCode::ArrowRight => NamedKey::ArrowRight,
KeyCode::ArrowDown => NamedKey::ArrowDown, KeyCode::ArrowDown => NamedKey::ArrowDown,
KeyCode::ArrowUp => NamedKey::ArrowUp, KeyCode::ArrowUp => NamedKey::ArrowUp,
KeyCode::ContextMenu => NamedKey::ContextMenu,
KeyCode::Lang2 => NamedKey::Eisu,
KeyCode::Lang1 => NamedKey::KanjiMode,
_ => return Key::Unidentified(NativeKey::MacOS(scancode)), _ => return Key::Unidentified(NativeKey::MacOS(scancode)),
}) })
} }
@@ -242,8 +236,8 @@ pub fn code_to_location(key: PhysicalKey) -> KeyLocation {
}; };
match code { match code {
KeyCode::MetaRight => KeyLocation::Right, KeyCode::SuperRight => KeyLocation::Right,
KeyCode::MetaLeft => KeyLocation::Left, KeyCode::SuperLeft => KeyLocation::Left,
KeyCode::ShiftLeft => KeyLocation::Left, KeyCode::ShiftLeft => KeyLocation::Left,
KeyCode::AltLeft => KeyLocation::Left, KeyCode::AltLeft => KeyLocation::Left,
KeyCode::ControlLeft => KeyLocation::Left, KeyCode::ControlLeft => KeyLocation::Left,
@@ -314,21 +308,28 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let mut state = ModifiersState::empty(); let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::empty(); let mut pressed_mods = ModifiersKeys::empty();
state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift)); state
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK)); pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK)); pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control)); state.set(
ModifiersState::CONTROL,
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
);
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK)); pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK)); pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option)); state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption));
pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK)); pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK));
pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK)); pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
state.set(ModifiersState::META, flags.contains(NSEventModifierFlags::Command)); state.set(
pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK)); ModifiersState::SUPER,
pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK)); flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
);
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
Modifiers { state, pressed_mods } Modifiers { state, pressed_mods }
} }
@@ -409,8 +410,8 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::Backquote => Some(0x32), KeyCode::Backquote => Some(0x32),
KeyCode::Backspace => Some(0x33), KeyCode::Backspace => Some(0x33),
KeyCode::Escape => Some(0x35), KeyCode::Escape => Some(0x35),
KeyCode::MetaRight => Some(0x36), KeyCode::SuperRight => Some(0x36),
KeyCode::MetaLeft => Some(0x37), KeyCode::SuperLeft => Some(0x37),
KeyCode::ShiftLeft => Some(0x38), KeyCode::ShiftLeft => Some(0x38),
KeyCode::CapsLock => Some(0x39), KeyCode::CapsLock => Some(0x39),
KeyCode::AltLeft => Some(0x3a), KeyCode::AltLeft => Some(0x3a),
@@ -555,8 +556,8 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x33 => KeyCode::Backspace, 0x33 => KeyCode::Backspace,
// 0x34 => unknown, // kVK_Powerbook_KeypadEnter // 0x34 => unknown, // kVK_Powerbook_KeypadEnter
0x35 => KeyCode::Escape, 0x35 => KeyCode::Escape,
0x36 => KeyCode::MetaRight, 0x36 => KeyCode::SuperRight,
0x37 => KeyCode::MetaLeft, 0x37 => KeyCode::SuperLeft,
0x38 => KeyCode::ShiftLeft, 0x38 => KeyCode::ShiftLeft,
0x39 => KeyCode::CapsLock, 0x39 => KeyCode::CapsLock,
0x3a => KeyCode::AltLeft, 0x3a => KeyCode::AltLeft,

View File

@@ -1,23 +1,29 @@
use std::any::Any; use std::any::Any;
use std::cell::Cell; use std::cell::Cell;
use std::fmt; use std::os::raw::c_void;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}; use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained}; use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::ProtocolObject; use objc2::{msg_send_id, sel, ClassType};
use objc2::{available, MainThreadMarker};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification, NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow, NSApplicationWillTerminateNotification, NSWindow,
}; };
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer; use super::super::notification_center::create_observer;
use super::app::override_send_event; use super::app::WinitApplication;
use super::app_state::AppState; use super::app_state::AppState;
use super::cursor::CustomCursor; use super::cursor::CustomCursor;
use super::event::dummy_event; use super::event::dummy_event;
@@ -27,25 +33,20 @@ use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError}; use crate::error::{EventLoopError, RequestError};
use crate::event_loop::{ use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
}; };
use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::macos::ActivationPolicy; use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus; use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window; use crate::platform_impl::Window;
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme}; use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
#[derive(Default)] #[derive(Default)]
pub struct PanicInfo { pub struct PanicInfo {
inner: Cell<Option<Box<dyn Any + Send + 'static>>>, inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
} }
impl fmt::Debug for PanicInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PanicInfo").finish_non_exhaustive()
}
}
// WARNING: // WARNING:
// As long as this struct is used through its `impl`, it is UnwindSafe. // As long as this struct is used through its `impl`, it is UnwindSafe.
// (If `get_mut` is called on `inner`, unwind safety may get broken.) // (If `get_mut` is called on `inner`, unwind safety may get broken.)
@@ -110,21 +111,17 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor( fn create_custom_cursor(
&self, &self,
source: CustomCursorSource, source: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> { ) -> Result<RootCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?))) Ok(RootCustomCursor { inner: CustomCursor::new(source.inner)? })
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new( Box::new(monitor::available_monitors().into_iter().map(|inner| RootMonitorHandle { inner }))
monitor::available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
} }
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> { fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor(); let monitor = monitor::primary_monitor();
Some(CoreMonitorHandle(Arc::new(monitor))) Some(RootMonitorHandle { inner: monitor })
} }
fn listen_device_events(&self, _allowed: DeviceEvents) {} fn listen_device_events(&self, _allowed: DeviceEvents) {}
@@ -132,8 +129,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn system_theme(&self) -> Option<Theme> { fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
// Dark appearance was introduced in macOS 10.14 if app.respondsToSelector(sel!(effectiveAppearance)) {
if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else { } else {
Some(Theme::Light) Some(Theme::Light)
@@ -172,7 +168,6 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
} }
} }
#[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
/// Store a reference to the application for convenience. /// Store a reference to the application for convenience.
/// ///
@@ -188,8 +183,8 @@ pub struct EventLoop {
// the system instead cleans it up next time it would have posted a notification to it. // the system instead cleans it up next time it would have posted a notification to it.
// //
// Though we do still need to keep the observers around to prevent them from being deallocated. // Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_finish_launching_observer: Retained<NSObject>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _will_terminate_observer: Retained<NSObject>,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -212,6 +207,16 @@ impl EventLoop {
let mtm = MainThreadMarker::new() let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!"); .expect("on macOS, `EventLoop` must be created on the main thread!");
let app: Retained<NSApplication> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
if !app.is_kind_of::<WinitApplication>() {
panic!(
"`winit` requires control over the principal class. You must create the event \
loop before other parts of your application initialize NSApplication"
);
}
let activation_policy = match attributes.activation_policy { let activation_policy = match attributes.activation_policy {
None => None, None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular), Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
@@ -226,12 +231,6 @@ impl EventLoop {
attributes.activate_ignoring_other_apps, attributes.activate_ignoring_other_apps,
); );
// Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm);
// Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app);
let center = unsafe { NSNotificationCenter::defaultCenter() }; let center = unsafe { NSNotificationCenter::defaultCenter() };
let weak_app_state = Rc::downgrade(&app_state); let weak_app_state = Rc::downgrade(&app_state);
@@ -285,10 +284,10 @@ impl EventLoop {
// redundant wake ups. // redundant wake ups.
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
app: A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
self.app_state.clear_exit(); self.app_state.clear_exit();
self.app_state.set_event_handler(app, || { self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| { autoreleasepool(|_| {
// clear / normalize pump_events state // clear / normalize pump_events state
self.app_state.set_wait_timeout(None); self.app_state.set_wait_timeout(None);
@@ -302,8 +301,8 @@ impl EventLoop {
self.app_state.dispatch_init_events(); self.app_state.dispatch_init_events();
} }
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing. // SAFETY: We do not run the application re-entrantly
self.app.run(); unsafe { self.app.run() };
// While the app is running it's possible that we catch a panic // While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which // to avoid unwinding across an objective-c ffi boundary, which
@@ -324,9 +323,9 @@ impl EventLoop {
pub fn pump_app_events<A: ApplicationHandler>( pub fn pump_app_events<A: ApplicationHandler>(
&mut self, &mut self,
timeout: Option<Duration>, timeout: Option<Duration>,
app: A, mut app: A,
) -> PumpStatus { ) -> PumpStatus {
self.app_state.set_event_handler(app, || { self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| { autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least // As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched. // run the loop until it has fully launched.
@@ -334,7 +333,8 @@ impl EventLoop {
debug_assert!(!self.app_state.is_running()); debug_assert!(!self.app_state.is_running());
self.app_state.set_stop_on_launch(); self.app_state.set_stop_on_launch();
self.app.run(); // SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched // has launched
@@ -366,7 +366,8 @@ impl EventLoop {
}, },
} }
self.app_state.set_stop_on_redraw(true); self.app_state.set_stop_on_redraw(true);
self.app.run(); // SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
} }
// While the app is running it's possible that we catch a panic // While the app is running it's possible that we catch a panic
@@ -407,22 +408,6 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
}); });
} }
/// Tell all windows to close.
///
/// This will synchronously trigger `WindowEvent::Destroyed` within
/// `windowWillClose:`, giving the application one last chance to handle
/// those events. It doesn't matter if the user also ends up closing the
/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
/// stays closed.
///
/// This ensures that no windows linger on after the event loop has exited,
/// see <https://github.com/rust-windowing/winit/issues/4135>.
pub(super) fn notify_windows_of_exit(app: &NSApplication) {
for window in app.windows() {
window.close();
}
}
/// Catches panics that happen inside `f` and when a panic /// Catches panics that happen inside `f` and when a panic
/// happens, stops the `sharedApplication` /// happens, stops the `sharedApplication`
#[inline] #[inline]
@@ -448,3 +433,62 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
}, },
} }
} }
#[derive(Debug)]
pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
}
}
}
impl EventLoopProxy {
pub(crate) fn new() -> Self {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// Let the main thread know there's a new event.
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}

View File

@@ -1,18 +1,62 @@
// TODO: Upstream these // TODO: Upstream these
#![allow(non_upper_case_globals)] #![allow(dead_code, non_snake_case, non_upper_case_globals)]
use std::ffi::c_void; use std::ffi::c_void;
use core_foundation::array::CFArrayRef;
use core_foundation::dictionary::CFDictionaryRef;
use core_foundation::string::CFStringRef;
use core_foundation::uuid::CFUUIDRef;
use core_graphics::base::CGError;
use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef};
use objc2::ffi::NSInteger; use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2_core_foundation::{cf_type, CFString, CFUUID};
use objc2_core_graphics::CGDirectDisplayID;
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
pub type CGDisplayFadeReservationToken = u32;
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
pub type Boolean = u8;
pub const FALSE: Boolean = 0;
pub const TRUE: Boolean = 1;
pub const kCGErrorSuccess: i32 = 0;
pub const kCGErrorFailure: i32 = 1000;
pub const kCGErrorIllegalArgument: i32 = 1001;
pub const kCGErrorInvalidConnection: i32 = 1002;
pub const kCGErrorInvalidContext: i32 = 1003;
pub const kCGErrorCannotComplete: i32 = 1004;
pub const kCGErrorNotImplemented: i32 = 1006;
pub const kCGErrorRangeCheck: i32 = 1007;
pub const kCGErrorTypeCheck: i32 = 1008;
pub const kCGErrorInvalidOperation: i32 = 1010;
pub const kCGErrorNoneAvailable: i32 = 1011;
pub const IO1BitIndexedPixels: &str = "P";
pub const IO2BitIndexedPixels: &str = "PP";
pub const IO4BitIndexedPixels: &str = "PPPP";
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB"; pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB"; pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB"; pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut c_void;
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework. // `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
// However, that framework was only introduced "publicly" in macOS 10.13. // However, that framework was only introduced "publicly" in macOS 10.13.
@@ -23,13 +67,54 @@ pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG // https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
#[link(name = "ApplicationServices", kind = "framework")] #[link(name = "ApplicationServices", kind = "framework")]
extern "C" { extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID; pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGDisplayGetDisplayIDFromUUID(uuid: &CFUUID) -> CGDirectDisplayID;
} }
#[link(name = "CoreGraphics", kind = "framework")] #[link(name = "CoreGraphics", kind = "framework")]
extern "C" { extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
pub fn CGConfigureDisplayFadeEffect(
config: CGDisplayConfigRef,
fadeOutSeconds: CGDisplayFadeInterval,
fadeInSeconds: CGDisplayFadeInterval,
fadeRed: f32,
fadeGreen: f32,
fadeBlue: f32,
) -> CGError;
pub fn CGAcquireDisplayFadeReservation(
seconds: CGDisplayReservationInterval,
token: *mut CGDisplayFadeReservationToken,
) -> CGError;
pub fn CGDisplayFade(
token: CGDisplayFadeReservationToken,
duration: CGDisplayFadeInterval,
startBlend: CGDisplayBlendFraction,
endBlend: CGDisplayBlendFraction,
redBlend: f32,
greenBlend: f32,
blueBlend: f32,
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,
mode: CGDisplayModeRef,
options: CFDictionaryRef,
) -> CGError;
pub fn CGDisplayCopyAllDisplayModes(
display: CGDirectDisplayID,
options: CFDictionaryRef,
) -> CFArrayRef;
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
// Wildly used private APIs; Apple uses them for their Terminal.app. // Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject; pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius( pub fn CGSSetWindowBackgroundBlurRadius(
@@ -39,12 +124,50 @@ extern "C" {
) -> i32; ) -> i32;
} }
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::*;
#[repr(transparent)] #[repr(transparent)]
pub struct TISInputSource(std::ffi::c_void); pub struct TISInputSource(std::ffi::c_void);
pub type TISInputSourceRef = *mut TISInputSource;
cf_type!(
unsafe impl TISInputSource {}
);
#[repr(transparent)] #[repr(transparent)]
pub struct UCKeyboardLayout(std::ffi::c_void); pub struct UCKeyboardLayout(std::ffi::c_void);
@@ -61,15 +184,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
#[link(name = "Carbon", kind = "framework")] #[link(name = "Carbon", kind = "framework")]
extern "C" { extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString; pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn TISGetInputSourceProperty( pub fn TISGetInputSourceProperty(
inputSource: &TISInputSource, inputSource: TISInputSourceRef,
propertyKey: &CFString, propertyKey: CFStringRef,
) -> *mut c_void; ) -> *mut c_void;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource; pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
pub fn LMGetKbdType() -> u8; pub fn LMGetKbdType() -> u8;
@@ -87,3 +210,45 @@ extern "C" {
unicodeString: *mut UniChar, unicodeString: *mut UniChar,
) -> OSStatus; ) -> OSStatus;
} }
// CGWindowLevel.h
//
// Note: There are two different things at play in this header:
// `CGWindowLevel` and `CGWindowLevelKey`.
//
// It seems like there was a push towards using "key" values instead of the
// raw window level values, and then you were supposed to use
// `CGWindowLevelForKey` to get the actual level.
//
// But the values that `NSWindowLevel` has are compiled in, and as such has
// to remain ABI compatible, so they're safe for us to hardcode as well.
#[allow(dead_code, non_upper_case_globals)]
mod window_level {
const kCGNumReservedWindowLevels: i32 = 16;
const kCGNumReservedBaseWindowLevels: i32 = 5;
pub const kCGBaseWindowLevel: i32 = i32::MIN;
pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels;
pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels;
pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20;
pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20;
pub const kCGBackstopMenuLevel: i32 = -20;
pub const kCGNormalWindowLevel: i32 = 0;
pub const kCGFloatingWindowLevel: i32 = 3;
pub const kCGTornOffMenuWindowLevel: i32 = 3;
pub const kCGModalPanelWindowLevel: i32 = 8;
pub const kCGUtilityWindowLevel: i32 = 19;
pub const kCGDockWindowLevel: i32 = 20;
pub const kCGMainMenuWindowLevel: i32 = 24;
pub const kCGStatusWindowLevel: i32 = 25;
pub const kCGPopUpMenuWindowLevel: i32 = 101;
pub const kCGOverlayWindowLevel: i32 = 102;
pub const kCGHelpWindowLevel: i32 = 200;
pub const kCGDraggingWindowLevel: i32 = 500;
pub const kCGScreenSaverWindowLevel: i32 = 1000;
pub const kCGAssistiveTechHighWindowLevel: i32 = 1500;
pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1;
}
pub use window_level::*;

View File

@@ -1,8 +1,8 @@
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{sel, MainThreadMarker}; use objc2::sel;
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{ns_string, NSProcessInfo, NSString}; use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
struct KeyEquivalent<'a> { struct KeyEquivalent<'a> {
key: &'a NSString, key: &'a NSString,
@@ -48,7 +48,10 @@ pub fn initialize(app: &NSApplication) {
Some(sel!(hideOtherApplications:)), Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent { Some(KeyEquivalent {
key: ns_string!("h"), key: ns_string!("h"),
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command), masks: Some(
NSEventModifierFlags::NSEventModifierFlagOption
| NSEventModifierFlags::NSEventModifierFlagCommand,
),
}), }),
); );

View File

@@ -14,10 +14,14 @@ mod view;
mod window; mod window;
mod window_delegate; mod window_delegate;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey}; pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
pub(crate) use self::event_loop::{ pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes, ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
}; };
pub(crate) use self::monitor::MonitorHandle; pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::Window; pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes; pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View File

@@ -1,38 +1,38 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt;
use std::num::{NonZeroU16, NonZeroU32}; use std::num::{NonZeroU16, NonZeroU32};
use std::ptr::NonNull;
use std::{fmt, ptr};
use dispatch2::run_on_main; use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
use objc2::rc::Retained; use core_foundation::base::{CFRelease, TCFType};
use objc2::MainThreadMarker; use core_foundation::string::CFString;
use objc2_app_kit::NSScreen; use core_graphics::display::{
use objc2_core_foundation::{CFArray, CFRetained, CFUUID}; CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
use objc2_core_graphics::{
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
CGDisplayMode, CGDisplayModelNumber, CGGetActiveDisplayList, CGMainDisplayID,
}; };
use objc2_core_video::{kCVReturnSuccess, CVDisplayLink, CVTimeFlags}; use objc2::rc::Retained;
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect}; use objc2::runtime::AnyObject;
use tracing::warn; use objc2_app_kit::NSScreen;
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
use super::ffi; use super::ffi;
use super::util::cgerr;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandleProvider, VideoMode};
#[derive(Clone)] #[derive(Clone)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
pub(crate) mode: VideoMode, size: PhysicalSize<u32>,
bit_depth: Option<NonZeroU16>,
refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
pub(crate) native_mode: NativeDisplayMode, pub(crate) native_mode: NativeDisplayMode,
} }
impl PartialEq for VideoModeHandle { impl PartialEq for VideoModeHandle {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.monitor == other.monitor && self.mode == other.mode self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
&& self.monitor == other.monitor
} }
} }
@@ -40,6 +40,9 @@ impl Eq for VideoModeHandle {}
impl std::hash::Hash for VideoModeHandle { impl std::hash::Hash for VideoModeHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.size.hash(state);
self.bit_depth.hash(state);
self.refresh_rate_millihertz.hash(state);
self.monitor.hash(state); self.monitor.hash(state);
} }
} }
@@ -47,28 +50,46 @@ impl std::hash::Hash for VideoModeHandle {
impl std::fmt::Debug for VideoModeHandle { impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoModeHandle") f.debug_struct("VideoModeHandle")
.field("mode", &self.mode) .field("size", &self.size)
.field("bit_depth", &self.bit_depth)
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
.field("monitor", &self.monitor) .field("monitor", &self.monitor)
.finish() .finish()
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
pub struct NativeDisplayMode(pub CFRetained<CGDisplayMode>);
unsafe impl Send for NativeDisplayMode {} unsafe impl Send for NativeDisplayMode {}
unsafe impl Sync for NativeDisplayMode {} unsafe impl Sync for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
ffi::CGDisplayModeRelease(self.0);
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
ffi::CGDisplayModeRetain(self.0);
}
NativeDisplayMode(self.0)
}
}
impl VideoModeHandle { impl VideoModeHandle {
fn new( fn new(
monitor: MonitorHandle, monitor: MonitorHandle,
native_mode: NativeDisplayMode, mode: NativeDisplayMode,
refresh_rate_millihertz: Option<NonZeroU32>, refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self { ) -> Self {
unsafe { unsafe {
#[allow(deprecated)]
let pixel_encoding = let pixel_encoding =
CGDisplayMode::pixel_encoding(Some(&native_mode.0)).unwrap().to_string(); CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode.0))
.to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32 32
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
@@ -79,139 +100,132 @@ impl VideoModeHandle {
unimplemented!() unimplemented!()
}; };
let mode = VideoMode { VideoModeHandle {
size: PhysicalSize::new( size: PhysicalSize::new(
CGDisplayMode::pixel_width(Some(&native_mode.0)) as u32, ffi::CGDisplayModeGetPixelWidth(mode.0) as u32,
CGDisplayMode::pixel_height(Some(&native_mode.0)) as u32, ffi::CGDisplayModeGetPixelHeight(mode.0) as u32,
), ),
refresh_rate_millihertz, refresh_rate_millihertz,
bit_depth: NonZeroU16::new(bit_depth), bit_depth: NonZeroU16::new(bit_depth),
}; monitor: monitor.clone(),
native_mode: mode,
}
}
}
VideoModeHandle { mode, monitor: monitor.clone(), native_mode } pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}
#[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID);
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
} }
} }
} }
/// `CGDirectDisplayID` is documented as: impl Eq for MonitorHandle {}
/// > a framebuffer, a color correction (gamma) table, and possibly an attached monitor.
/// impl PartialOrd for MonitorHandle {
/// That is, it doesn't actually represent the monitor itself. Instead, we use the UUID of the fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
/// monitor, as retrieved from `CGDisplayCreateUUIDFromDisplayID` (this makes the monitor ID stable, Some(self.cmp(other))
/// even across reboots and video mode changes). }
/// }
/// NOTE: I'd be perfectly valid to store `[u8; 16]` in here instead, we only store `CFUUID` to
/// avoid having to re-create it when we want to fetch the display ID. impl Ord for MonitorHandle {
#[derive(Clone, PartialEq, Eq, Hash)] fn cmp(&self, other: &Self) -> std::cmp::Ordering {
pub struct MonitorHandle(CFRetained<CFUUID>); unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
}
}
pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
for display in displays {
monitors.push_back(MonitorHandle(display));
}
monitors
} else {
VecDeque::with_capacity(0)
}
}
pub fn primary_monitor() -> MonitorHandle {
MonitorHandle(CGDisplay::main().id)
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle")
.field("name", &self.name())
.field("native_identifier", &self.native_identifier())
.field("position", &self.position())
.field("scale_factor", &self.scale_factor())
.finish_non_exhaustive()
}
}
impl MonitorHandle { impl MonitorHandle {
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle. pub fn new(id: CGDirectDisplayID) -> Self {
fn uuid(&self) -> u128 { MonitorHandle(id)
u128::from_ne_bytes(self.0.uuid_bytes().into())
}
fn display_id(&self) -> CGDirectDisplayID {
unsafe { ffi::CGDisplayGetDisplayIDFromUUID(&self.0) }
}
#[track_caller]
pub(crate) fn new(display_id: CGDirectDisplayID) -> Option<Self> {
// kCGNullDirectDisplay
if display_id == 0 {
// `CGDisplayCreateUUIDFromDisplayID` checks kCGNullDirectDisplay internally.
warn!("constructing monitor from invalid display ID 0; falling back to main monitor");
}
// SAFETY: Valid to call.
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(display_id) };
let ptr = NonNull::new(ptr)?;
// SAFETY: `CGDisplayCreateUUIDFromDisplayID` is a "create" function, so the pointer has
// +1 retain count.
let uuid = unsafe { CFRetained::from_raw(ptr) };
Some(Self(uuid))
}
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.display_id()) }.unwrap());
refresh_rate_millihertz(self.display_id(), &current_display_mode)
}
pub fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> {
let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone();
let array = unsafe { CGDisplayCopyAllDisplayModes(self.display_id(), None) };
let modes = if let Some(array) = array {
// SAFETY: `CGDisplayCopyAllDisplayModes` is documented to return an array of
// display modes.
unsafe { CFRetained::cast_unchecked::<CFArray<CGDisplayMode>>(array) }
} else {
// Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor during
// sleep/wake/cycling monitors. It tends to have null or 1 video mode only.
// See <https://github.com/bevyengine/bevy/issues/17827>.
warn!(monitor = ?self, "failed to get a list of display modes");
CFArray::empty()
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = unsafe { CGDisplayMode::refresh_rate(Some(&mode)) };
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0.0 {
NonZeroU32::new((cg_refresh_rate_hertz * 1000.0).round() as u32)
} else {
refresh_rate_millihertz
};
VideoModeHandle::new(monitor.clone(), NativeDisplayMode(mode), refresh_rate_millihertz)
})
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = self.uuid();
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
if let Some(other) = MonitorHandle::new(other_native_id) {
uuid == other.uuid()
} else {
// Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(other_native_id, "comparing against screen with invalid display ID");
false
}
})
}
}
impl MonitorHandleProvider for MonitorHandle {
fn id(&self) -> u128 {
self.uuid()
}
fn native_id(&self) -> u64 {
self.display_id() as _
} }
// TODO: Be smarter about this: // TODO: Be smarter about this:
//
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126> // <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
fn name(&self) -> Option<std::borrow::Cow<'_, str>> { pub fn name(&self) -> Option<String> {
let screen_num = unsafe { CGDisplayModelNumber(self.display_id()) }; let MonitorHandle(display_id) = *self;
Some(format!("Monitor #{screen_num}").into()) let screen_num = CGDisplay::new(display_id).model_number();
Some(format!("Monitor #{screen_num}"))
} }
fn position(&self) -> Option<PhysicalPosition<i32>> { #[inline]
pub fn native_identifier(&self) -> u32 {
self.0
}
#[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
// This is already in screen coordinates. If we were using `NSScreen`, // This is already in screen coordinates. If we were using `NSScreen`,
// then a conversion would've been needed: // then a conversion would've been needed:
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame()) // flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
let bounds = unsafe { CGDisplayBounds(self.display_id()) }; let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y); let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor())) Some(position.to_physical(self.scale_factor()))
} }
fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
run_on_main(|mtm| { run_on_main(|mtm| {
match self.ns_screen(mtm) { match self.ns_screen(mtm) {
Some(screen) => screen.backingScaleFactor() as f64, Some(screen) => screen.backingScaleFactor() as f64,
@@ -220,58 +234,67 @@ impl MonitorHandleProvider for MonitorHandle {
}) })
} }
fn current_video_mode(&self) -> Option<VideoMode> { fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let mode = let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.display_id()) }.unwrap()); NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
let refresh_rate_millihertz = refresh_rate_millihertz(self.display_id(), &mode); refresh_rate_millihertz(self.0, &current_display_mode)
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
} }
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> { pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
Box::new(self.video_mode_handles().map(|mode| mode.mode)) let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
} let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
} Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz))
pub fn available_monitors() -> VecDeque<MonitorHandle> {
let mut expected_count = 0;
let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) });
if res.is_err() {
return VecDeque::with_capacity(0);
} }
let mut displays: Vec<CGDirectDisplayID> = vec![0; expected_count as usize]; pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
let mut actual_count = 0; let refresh_rate_millihertz = self.refresh_rate_millihertz();
let res = cgerr(unsafe { let monitor = self.clone();
CGGetActiveDisplayList(expected_count, displays.as_mut_ptr(), &mut actual_count)
});
displays.truncate(actual_count as usize);
if res.is_err() { unsafe {
return VecDeque::with_capacity(0); let modes = {
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
NonZeroU32::new((cg_refresh_rate_hertz * 1000) as u32)
} else {
refresh_rate_millihertz
};
VideoModeHandle::new(
monitor.clone(),
NativeDisplayMode(mode),
refresh_rate_millihertz,
)
})
}
} }
let mut monitors = VecDeque::with_capacity(displays.len()); pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
for display in displays { let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
// Display ID just fetched from `CGGetActiveDisplayList`, should be fine to unwrap. NSScreen::screens(mtm).into_iter().find(|screen| {
monitors.push_back(MonitorHandle::new(display).expect("invalid display ID")); let other_native_id = get_display_id(screen);
} let other_uuid = unsafe {
monitors ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
} };
uuid == other_uuid
pub fn primary_monitor() -> MonitorHandle { })
// Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap.
MonitorHandle::new(unsafe { CGMainDisplayID() }).expect("invalid display ID")
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle")
.field("name", &self.name())
.field("uuid", &self.uuid())
.field("display_id", &self.display_id())
.field("position", &self.position())
.field("scale_factor", &self.scale_factor())
.finish_non_exhaustive()
} }
} }
@@ -283,14 +306,15 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
// Retrieve the CGDirectDisplayID associated with this screen // Retrieve the CGDirectDisplayID associated with this screen
// //
// The value from @"NSScreenNumber" in deviceDescription is guaranteed // SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for details: // to be an NSNumber. See documentation for `deviceDescription` for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc> // <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description let obj = device_description
.objectForKey(key) .get(key)
.expect("failed getting screen display id from device description") .expect("failed getting screen display id from device description");
.downcast::<NSNumber>() let obj: *const AnyObject = obj;
.expect("NSScreenNumber must be NSNumber"); let obj: *const NSNumber = obj.cast();
let obj: &NSNumber = unsafe { &*obj };
obj.as_u32() obj.as_u32()
}) })
@@ -311,7 +335,7 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
// It is intentional that we use `CGMainDisplayID` (as opposed to // It is intentional that we use `CGMainDisplayID` (as opposed to
// `NSScreen::mainScreen`), because that's what the screen coordinates // `NSScreen::mainScreen`), because that's what the screen coordinates
// are relative to, no matter which display the window is currently on. // are relative to, no matter which display the window is currently on.
let main_screen_height = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height; let main_screen_height = CGDisplay::main().bounds().size.height;
let y = main_screen_height - frame.size.height - frame.origin.y; let y = main_screen_height - frame.size.height - frame.origin.y;
NSPoint::new(frame.origin.x, y) NSPoint::new(frame.origin.x, y)
@@ -319,81 +343,26 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> { fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
unsafe { unsafe {
let refresh_rate = CGDisplayMode::refresh_rate(Some(&mode.0)); let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0);
if refresh_rate > 0.0 { if refresh_rate > 0.0 {
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32); return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
} }
let mut display_link = std::ptr::null_mut(); let mut display_link = std::ptr::null_mut();
#[allow(deprecated)] if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess {
if CVDisplayLink::create_with_cg_display(id, NonNull::from(&mut display_link))
!= kCVReturnSuccess
{
return None; return None;
} }
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap()); let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
#[allow(deprecated)] ffi::CVDisplayLinkRelease(display_link);
let time = display_link.nominal_output_video_refresh_period();
// This value is indefinite if an invalid display link was specified // This value is indefinite if an invalid display link was specified
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 { if time.flags & ffi::kCVTimeIsIndefinite != 0 {
return None; return None;
} }
(time.timeScale as i64) (time.time_scale as i64)
.checked_div(time.timeValue) .checked_div(time.time_value)
.map(|v| (v * 1000) as u32) .map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new) .and_then(NonZeroU32::new)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uuid_stable() {
let handle_a = MonitorHandle::new(1).unwrap();
let handle_b = MonitorHandle::new(1).unwrap();
assert_eq!(handle_a, handle_b);
assert_eq!(handle_a.display_id(), handle_b.display_id());
assert_eq!(handle_a.uuid(), handle_b.uuid());
let handle_a = primary_monitor();
let handle_b = primary_monitor();
assert_eq!(handle_a, handle_b);
assert_eq!(handle_a.display_id(), handle_b.display_id());
assert_eq!(handle_a.uuid(), handle_b.uuid());
}
/// Test the MonitorHandle::new fallback.
#[test]
fn monitorhandle_from_zero() {
let handle0 = MonitorHandle::new(0).unwrap();
let handle1 = MonitorHandle::new(1).unwrap();
assert_eq!(handle0, handle1);
assert_eq!(handle0.display_id(), handle1.display_id());
assert_eq!(handle0.uuid(), handle1.uuid());
}
#[test]
fn from_invalid_id() {
// Assume there are never this many monitors connected.
assert!(MonitorHandle::new(10000).is_none());
}
/// Test that calling `CGDisplayGetDisplayIDFromUUID` on an invalid UUID returns an invalid
/// display ID.
#[test]
fn invalid_monitor_handle() {
// `CGMainDisplayID` must be called to avoid:
// ```
// Assertion failed: (did_initialize), function CGS_REQUIRE_INIT, file CGInitialization.c, line 44.
// ```
// See https://github.com/JXA-Cookbook/JXA-Cookbook/issues/27#issuecomment-277517668
let _ = unsafe { CGMainDisplayID() };
let handle = MonitorHandle(CFUUID::new(None).unwrap());
assert_eq!(handle.display_id(), 0);
}
}

View File

@@ -9,16 +9,22 @@ use std::ptr;
use std::rc::Weak; use std::rc::Weak;
use std::time::Instant; use std::time::Instant;
use objc2::MainThreadMarker; use block2::Block;
use objc2_core_foundation::{ use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained, use core_foundation::date::CFAbsoluteTimeGetCurrent;
CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverCallBack, use core_foundation::runloop::{
CFRunLoopObserverContext, CFRunLoopTimer, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
}; };
use objc2_foundation::MainThreadMarker;
use tracing::error; use tracing::error;
use super::app_state::AppState; use super::app_state::AppState;
use super::event_loop::{stop_app_on_panic, PanicInfo}; use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F) unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where where
@@ -42,8 +48,8 @@ where
} }
// begin is queued with the highest priority to ensure it is processed before other observers // begin is queued with the highest priority to ensure it is processed before other observers
extern "C-unwind" fn control_flow_begin_handler( extern "C" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver, _: CFRunLoopObserverRef,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
panic_info: *mut c_void, panic_info: *mut c_void,
) { ) {
@@ -51,7 +57,7 @@ extern "C-unwind" fn control_flow_begin_handler(
control_flow_handler(panic_info, |panic_info| { control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
CFRunLoopActivity::AfterWaiting => { kCFRunLoopAfterWaiting => {
// trace!("Triggered `CFRunLoopAfterWaiting`"); // trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info); AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
// trace!("Completed `CFRunLoopAfterWaiting`"); // trace!("Completed `CFRunLoopAfterWaiting`");
@@ -64,8 +70,8 @@ extern "C-unwind" fn control_flow_begin_handler(
// end is queued with the lowest priority to ensure it is processed after other observers // end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopExiting would get sent after AboutToWait // without that, LoopExiting would get sent after AboutToWait
extern "C-unwind" fn control_flow_end_handler( extern "C" fn control_flow_end_handler(
_: *mut CFRunLoopObserver, _: CFRunLoopObserverRef,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
panic_info: *mut c_void, panic_info: *mut c_void,
) { ) {
@@ -73,12 +79,12 @@ extern "C-unwind" fn control_flow_end_handler(
control_flow_handler(panic_info, |panic_info| { control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
CFRunLoopActivity::BeforeWaiting => { kCFRunLoopBeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`"); // trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info); AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`"); // trace!("Completed `CFRunLoopBeforeWaiting`");
}, },
CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */ kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(), _ => unreachable!(),
} }
}); });
@@ -86,32 +92,44 @@ extern "C-unwind" fn control_flow_end_handler(
} }
#[derive(Debug)] #[derive(Debug)]
pub struct RunLoop(CFRetained<CFRunLoop>); pub struct RunLoop(CFRunLoopRef);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
impl RunLoop { impl RunLoop {
pub fn main(mtm: MainThreadMarker) -> Self { pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so // SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed. // scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm; let _ = mtm;
RunLoop(CFRunLoop::main().unwrap()) RunLoop(unsafe { CFRunLoopGetMain() })
} }
pub fn wakeup(&self) { pub fn wakeup(&self) {
self.0.wake_up(); unsafe { CFRunLoopWakeUp(self.0) }
} }
unsafe fn add_observer( unsafe fn add_observer(
&self, &self,
flags: CFRunLoopActivity, flags: CFOptionFlags,
// The lower the value, the sooner this will run
priority: CFIndex, priority: CFIndex,
handler: CFRunLoopObserverCallBack, handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext, context: *mut CFRunLoopObserverContext,
) { ) {
let observer = let observer = unsafe {
unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) } CFRunLoopObserverCreate(
.unwrap(); ptr::null_mut(),
self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes }); flags,
ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
handler,
context,
)
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
} }
/// Submit a closure to run on the main thread as the next step in the run loop, before other /// Submit a closure to run on the main thread as the next step in the run loop, before other
@@ -148,6 +166,10 @@ impl RunLoop {
/// put the event at the very front of the queue, to be handled as soon as possible after /// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling. /// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) { pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
extern "C" {
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
}
// Convert `FnOnce()` to `Block<dyn Fn()>`. // Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure)); let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || { let block = block2::RcBlock::new(move || {
@@ -173,10 +195,10 @@ impl RunLoop {
// and be delivered to the application afterwards. // and be delivered to the application afterwards.
// //
// [#1779]: https://github.com/rust-windowing/winit/issues/1779 // [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() }; let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`. // SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { self.0.perform_block(Some(mode), Some(&block)) } unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
} }
} }
@@ -191,15 +213,15 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
copyDescription: None, copyDescription: None,
}; };
run_loop.add_observer( run_loop.add_observer(
CFRunLoopActivity::AfterWaiting, kCFRunLoopAfterWaiting,
CFIndex::MIN, CFIndex::MIN,
Some(control_flow_begin_handler), control_flow_begin_handler,
&mut context as *mut _, &mut context as *mut _,
); );
run_loop.add_observer( run_loop.add_observer(
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting, kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::MAX, CFIndex::MAX,
Some(control_flow_end_handler), control_flow_end_handler,
&mut context as *mut _, &mut context as *mut _,
); );
} }
@@ -207,7 +229,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoopWaker { pub struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>, timer: CFRunLoopTimerRef,
/// An arbitrary instant in the past, that will trigger an immediate wake /// An arbitrary instant in the past, that will trigger an immediate wake
/// We save this as the `next_fire_date` for consistency so we can /// We save this as the `next_fire_date` for consistency so we can
@@ -222,28 +244,30 @@ pub struct EventLoopWaker {
impl Drop for EventLoopWaker { impl Drop for EventLoopWaker {
fn drop(&mut self) { fn drop(&mut self) {
self.timer.invalidate(); unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
} }
} }
impl EventLoopWaker { impl EventLoopWaker {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {} extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe { unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the // It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching // future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimer::new( let timer = CFRunLoopTimerCreate(
None, ptr::null_mut(),
f64::MAX, f64::MAX,
0.000_000_1, 0.000_000_1,
0, 0,
0, 0,
Some(wakeup_main_loop), wakeup_main_loop,
ptr::null_mut(), ptr::null_mut(),
) );
.unwrap(); CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
CFRunLoop::main().unwrap().add_timer(Some(&timer), kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None } Self { timer, start_instant: Instant::now(), next_fire_date: None }
} }
} }
@@ -251,14 +275,14 @@ impl EventLoopWaker {
pub fn stop(&mut self) { pub fn stop(&mut self) {
if self.next_fire_date.is_some() { if self.next_fire_date.is_some() {
self.next_fire_date = None; self.next_fire_date = None;
self.timer.set_next_fire_date(f64::MAX); unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
} }
} }
pub fn start(&mut self) { pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) { if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant); self.next_fire_date = Some(self.start_instant);
self.timer.set_next_fire_date(f64::MIN); unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
} }
} }
@@ -271,11 +295,13 @@ impl EventLoopWaker {
Some(instant) => { Some(instant) => {
if self.next_fire_date != Some(instant) { if self.next_fire_date != Some(instant) {
self.next_fire_date = Some(instant); self.next_fire_date = Some(instant);
let current = CFAbsoluteTimeGetCurrent(); unsafe {
let duration = instant - now; let current = CFAbsoluteTimeGetCurrent();
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 let duration = instant - now;
+ duration.as_secs() as f64; let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
self.timer.set_next_fire_date(current + fsecs); + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
} }
}, },
None => { None => {

View File

@@ -1,8 +1,5 @@
use objc2_core_graphics::CGError;
use tracing::trace; use tracing::trace;
use crate::error::OsError;
macro_rules! trace_scope { macro_rules! trace_scope {
($s:literal) => { ($s:literal) => {
let _crate = let _crate =
@@ -29,12 +26,3 @@ impl Drop for TraceGuard {
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn); trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
} }
} }
#[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success {
Ok(())
} else {
Err(os_error!(format!("CGError {err:?}")))
}
}

View File

@@ -6,23 +6,24 @@ use std::rc::Rc;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker}; use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSWindow, NSTrackingRectTag, NSView,
}; };
use objc2_foundation::{ use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
NSSize, NSString, NSUInteger,
}; };
use super::app_state::AppState; use super::app_state::AppState;
use super::cursor::{default_cursor, invisible_cursor}; use super::cursor::{default_cursor, invisible_cursor};
use super::event::{ use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey, scancode_to_physicalkey, KeyEventExtra,
}; };
use super::window::window_id; use super::window::WinitWindow;
use crate::dpi::{LogicalPosition, LogicalSize}; use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{ use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
@@ -81,7 +82,7 @@ fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
match key { match key {
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT), Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL), Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
Key::Named(NamedKey::Meta) => Some(ModifiersState::META), Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT), Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
_ => None, _ => None,
} }
@@ -92,7 +93,7 @@ fn get_right_modifier_code(key: &Key) -> KeyCode {
Key::Named(NamedKey::Alt) => KeyCode::AltRight, Key::Named(NamedKey::Alt) => KeyCode::AltRight,
Key::Named(NamedKey::Control) => KeyCode::ControlRight, Key::Named(NamedKey::Control) => KeyCode::ControlRight,
Key::Named(NamedKey::Shift) => KeyCode::ShiftRight, Key::Named(NamedKey::Shift) => KeyCode::ShiftRight,
Key::Named(NamedKey::Meta) => KeyCode::MetaRight, Key::Named(NamedKey::Super) => KeyCode::SuperRight,
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -102,7 +103,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
Key::Named(NamedKey::Alt) => KeyCode::AltLeft, Key::Named(NamedKey::Alt) => KeyCode::AltLeft,
Key::Named(NamedKey::Control) => KeyCode::ControlLeft, Key::Named(NamedKey::Control) => KeyCode::ControlLeft,
Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft, Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft,
Key::Named(NamedKey::Meta) => KeyCode::MetaLeft, Key::Named(NamedKey::Super) => KeyCode::SuperLeft,
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -137,21 +138,28 @@ pub struct ViewState {
option_as_alt: Cell<OptionAsAlt>, option_as_alt: Cell<OptionAsAlt>,
} }
define_class!( declare_class!(
#[unsafe(super(NSView, NSResponder, NSObject))]
#[ivars = ViewState]
#[name = "WinitView"]
pub(super) struct WinitView; pub(super) struct WinitView;
/// This documentation attribute makes rustfmt work for some reason? unsafe impl ClassType for WinitView {
impl WinitView { #[inherits(NSResponder, NSObject)]
#[unsafe(method(isFlipped))] type Super = NSView;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitView";
}
impl DeclaredClass for WinitView {
type Ivars = ViewState;
}
unsafe impl WinitView {
#[method(isFlipped)]
fn is_flipped(&self) -> bool { fn is_flipped(&self) -> bool {
// `winit` uses the upper-left corner as the origin. // `winit` uses the upper-left corner as the origin.
true true
} }
#[unsafe(method(viewDidMoveToWindow))] #[method(viewDidMoveToWindow)]
fn view_did_move_to_window(&self) { fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow"); trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() { if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -167,7 +175,7 @@ define_class!(
} }
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`. // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[unsafe(method(viewFrameDidChangeNotification:))] #[method(viewFrameDidChangeNotification:)]
fn frame_did_change(&self, _notification: Option<&AnyObject>) { fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification"); trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() { if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -182,45 +190,38 @@ define_class!(
self.ivars().tracking_rect.set(Some(tracking_rect)); self.ivars().tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because: // Emit resize event here rather than from windowDidResize because:
// 1. When a new window is created as a tab, the frame size may change without a window // 1. When a new window is created as a tab, the frame size may change without a window resize occurring.
// resize occurring. // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height).
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor()); let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::SurfaceResized(size)); self.queue_event(WindowEvent::SurfaceResized(size));
} }
#[unsafe(method(drawRect:))] #[method(drawRect:)]
fn draw_rect(&self, _rect: NSRect) { fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:"); trace_scope!("drawRect:");
self.ivars().app_state.handle_redraw(window_id(&self.window())); self.ivars().app_state.handle_redraw(self.window().id());
// This is a direct subclass of NSView, no need to call superclass' drawRect: // This is a direct subclass of NSView, no need to call superclass' drawRect:
} }
#[unsafe(method(acceptsFirstResponder))] #[method(acceptsFirstResponder)]
fn accepts_first_responder(&self) -> bool { fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder"); trace_scope!("acceptsFirstResponder");
true true
} }
// This is necessary to prevent a beefy terminal error on MacBook Pros: // This is necessary to prevent a beefy terminal error on MacBook Pros:
// IMKInputSession [0x7fc573576ff0 // IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
// presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self // TODO: Add an API extension for using `NSTouchBar`
// textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error #[method_id(touchBar)]
// Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this
// process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from
// this process.}, com.apple.inputmethod.EmojiFunctionRowItem TODO: Add an API
// extension for using `NSTouchBar`
#[unsafe(method_id(touchBar))]
fn touch_bar(&self) -> Option<Retained<NSObject>> { fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar"); trace_scope!("touchBar");
None None
} }
#[unsafe(method(resetCursorRects))] #[method(resetCursorRects)]
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects"); trace_scope!("resetCursorRects");
let bounds = self.bounds(); let bounds = self.bounds();
@@ -235,13 +236,13 @@ define_class!(
} }
unsafe impl NSTextInputClient for WinitView { unsafe impl NSTextInputClient for WinitView {
#[unsafe(method(hasMarkedText))] #[method(hasMarkedText)]
fn has_marked_text(&self) -> bool { fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText"); trace_scope!("hasMarkedText");
self.ivars().marked_text.borrow().length() > 0 self.ivars().marked_text.borrow().length() > 0
} }
#[unsafe(method(markedRange))] #[method(markedRange)]
fn marked_range(&self) -> NSRange { fn marked_range(&self) -> NSRange {
trace_scope!("markedRange"); trace_scope!("markedRange");
let length = self.ivars().marked_text.borrow().length(); let length = self.ivars().marked_text.borrow().length();
@@ -253,14 +254,14 @@ define_class!(
} }
} }
#[unsafe(method(selectedRange))] #[method(selectedRange)]
fn selected_range(&self) -> NSRange { fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange"); trace_scope!("selectedRange");
// Documented to return `{NSNotFound, 0}` if there is no selection. // Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0) NSRange::new(NSNotFound as NSUInteger, 0)
} }
#[unsafe(method(setMarkedText:selectedRange:replacementRange:))] #[method(setMarkedText:selectedRange:replacementRange:)]
fn set_marked_text( fn set_marked_text(
&self, &self,
string: &NSObject, string: &NSObject,
@@ -270,15 +271,23 @@ define_class!(
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:"); trace_scope!("setMarkedText:selectedRange:replacementRange:");
let (marked_text, string) = if let Some(string) = // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
string.downcast_ref::<NSAttributedString>() let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
{ let string: *const NSObject = string;
(NSMutableAttributedString::from_attributed_nsstring(string), string.string()) let string: *const NSAttributedString = string.cast();
} else if let Some(string) = string.downcast_ref::<NSString>() { let string = unsafe { &*string };
(NSMutableAttributedString::from_nsstring(string), string.copy()) (
NSMutableAttributedString::from_attributed_nsstring(string),
string.string(),
)
} else { } else {
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let string: *const NSObject = string;
panic!("unexpected text {string:?}") let string: *const NSString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
)
}; };
// Update marked text. // Update marked text.
@@ -314,7 +323,7 @@ define_class!(
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range))); self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
} }
#[unsafe(method(unmarkText))] #[method(unmarkText)]
fn unmark_text(&self) { fn unmark_text(&self) {
trace_scope!("unmarkText"); trace_scope!("unmarkText");
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
@@ -331,13 +340,13 @@ define_class!(
} }
} }
#[unsafe(method_id(validAttributesForMarkedText))] #[method_id(validAttributesForMarkedText)]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> { fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText"); trace_scope!("validAttributesForMarkedText");
NSArray::new() NSArray::new()
} }
#[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))] #[method_id(attributedSubstringForProposedRange:actualRange:)]
fn attributed_substring_for_proposed_range( fn attributed_substring_for_proposed_range(
&self, &self,
_range: NSRange, _range: NSRange,
@@ -347,36 +356,42 @@ define_class!(
None None
} }
#[unsafe(method(characterIndexForPoint:))] #[method(characterIndexForPoint:)]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:"); trace_scope!("characterIndexForPoint:");
0 0
} }
#[unsafe(method(firstRectForCharacterRange:actualRange:))] #[method(firstRectForCharacterRange:actualRange:)]
fn first_rect_for_character_range( fn first_rect_for_character_range(
&self, &self,
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> NSRect { ) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:"); trace_scope!("firstRectForCharacterRange:actualRange:");
let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get()); let rect = NSRect::new(
self.ivars().ime_position.get(),
self.ivars().ime_size.get()
);
// Return value is expected to be in screen coordinates, so we need a conversion here // Return value is expected to be in screen coordinates, so we need a conversion here
self.window().convertRectToScreen(self.convertRect_toView(rect, None)) self.window()
.convertRectToScreen(self.convertRect_toView(rect, None))
} }
#[unsafe(method(insertText:replacementRange:))] #[method(insertText:replacementRange:)]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:"); trace_scope!("insertText:replacementRange:");
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() { // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
string.string().to_string() let string = if string.is_kind_of::<NSAttributedString>() {
} else if let Some(string) = string.downcast_ref::<NSString>() { let string: *const NSObject = string;
string.to_string() let string: *const NSAttributedString = string.cast();
unsafe { &*string }.string().to_string()
} else { } else {
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let string: *const NSObject = string;
panic!("unexpected text {string:?}") let string: *const NSString = string.cast();
unsafe { &*string }.to_string()
}; };
let is_control = string.chars().next().is_some_and(|c| c.is_control()); let is_control = string.chars().next().is_some_and(|c| c.is_control());
@@ -389,16 +404,15 @@ define_class!(
} }
} }
// Basically, we're sent this message whenever a keyboard event that doesn't generate a // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C. // readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[unsafe(method(doCommandBySelector:))] #[method(doCommandBySelector:)]
fn do_command_by_selector(&self, command: Sel) { fn do_command_by_selector(&self, command: Sel) {
trace_scope!("doCommandBySelector:"); trace_scope!("doCommandBySelector:");
// We shouldn't forward any character from just committed text, since we'll end up // We shouldn't forward any character from just committed text, since we'll end up sending
// sending it twice with some IMEs like Korean one. We'll also always send // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
// `Enter` in that case, which is not desired given it was used to confirm // which is not desired given it was used to confirm IME input.
// IME input.
if self.ivars().ime_state.get() == ImeState::Committed { if self.ivars().ime_state.get() == ImeState::Committed {
return; return;
} }
@@ -412,14 +426,10 @@ define_class!(
} }
// Send command action to user if they requested it. // Send command action to user if they requested it.
let window_id = window_id(&self.window()); let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
if let Some(handler) = app.macos_handler() { if let Some(handler) = app.macos_handler() {
handler.standard_key_binding( handler.standard_key_binding(event_loop, window_id, command.name());
event_loop,
window_id,
command.name().to_str().unwrap(),
);
} }
}); });
@@ -429,9 +439,8 @@ define_class!(
} }
} }
/// This documentation attribute makes rustfmt work for some reason? unsafe impl WinitView {
impl WinitView { #[method(keyDown:)]
#[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) { fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:"); trace_scope!("keyDown:");
{ {
@@ -474,7 +483,7 @@ define_class!(
// Allow normal input after the commit. // Allow normal input after the commit.
self.ivars().ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
true true
}, }
ImeState::Preedit => true, ImeState::Preedit => true,
// `key_down` could result in preedit clear, so compare old and current state. // `key_down` could result in preedit clear, so compare old and current state.
_ => old_ime_state != self.ivars().ime_state.get(), _ => old_ime_state != self.ivars().ime_state.get(),
@@ -490,7 +499,7 @@ define_class!(
} }
} }
#[unsafe(method(keyUp:))] #[method(keyUp:)]
fn key_up(&self, event: &NSEvent) { fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:"); trace_scope!("keyUp:");
@@ -498,7 +507,10 @@ define_class!(
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
// We want to send keyboard input when we are currently in the ground state. // We want to send keyboard input when we are currently in the ground state.
if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) { if matches!(
self.ivars().ime_state.get(),
ImeState::Ground | ImeState::Disabled
) {
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: None, device_id: None,
event: create_key_event(&event, false, false), event: create_key_event(&event, false, false),
@@ -507,14 +519,14 @@ define_class!(
} }
} }
#[unsafe(method(flagsChanged:))] #[method(flagsChanged:)]
fn flags_changed(&self, event: &NSEvent) { fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:"); trace_scope!("flagsChanged:");
self.update_modifiers(event, true); self.update_modifiers(event, true);
} }
#[unsafe(method(insertTab:))] #[method(insertTab:)]
fn insert_tab(&self, _sender: Option<&AnyObject>) { fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:"); trace_scope!("insertTab:");
let window = self.window(); let window = self.window();
@@ -525,7 +537,7 @@ define_class!(
} }
} }
#[unsafe(method(insertBackTab:))] #[method(insertBackTab:)]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) { fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:"); trace_scope!("insertBackTab:");
let window = self.window(); let window = self.window();
@@ -538,7 +550,7 @@ define_class!(
// Allows us to receive Cmd-. (the shortcut for closing a dialog) // Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
#[unsafe(method(cancelOperation:))] #[method(cancelOperation:)]
fn cancel_operation(&self, _sender: Option<&AnyObject>) { fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self); let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:"); trace_scope!("cancelOperation:");
@@ -568,42 +580,42 @@ define_class!(
// //
// See https://github.com/rust-windowing/winit/pull/1490 for history. // See https://github.com/rust-windowing/winit/pull/1490 for history.
#[unsafe(method(mouseDown:))] #[method(mouseDown:)]
fn mouse_down(&self, event: &NSEvent) { fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:"); trace_scope!("mouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(mouseUp:))] #[method(mouseUp:)]
fn mouse_up(&self, event: &NSEvent) { fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:"); trace_scope!("mouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[unsafe(method(rightMouseDown:))] #[method(rightMouseDown:)]
fn right_mouse_down(&self, event: &NSEvent) { fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:"); trace_scope!("rightMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(rightMouseUp:))] #[method(rightMouseUp:)]
fn right_mouse_up(&self, event: &NSEvent) { fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:"); trace_scope!("rightMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[unsafe(method(otherMouseDown:))] #[method(otherMouseDown:)]
fn other_mouse_down(&self, event: &NSEvent) { fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:"); trace_scope!("otherMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(otherMouseUp:))] #[method(otherMouseUp:)]
fn other_mouse_up(&self, event: &NSEvent) { fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:"); trace_scope!("otherMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
@@ -612,27 +624,27 @@ define_class!(
// No tracing on these because that would be overly verbose // No tracing on these because that would be overly verbose
#[unsafe(method(mouseMoved:))] #[method(mouseMoved:)]
fn mouse_moved(&self, event: &NSEvent) { fn mouse_moved(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(mouseDragged:))] #[method(mouseDragged:)]
fn mouse_dragged(&self, event: &NSEvent) { fn mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(rightMouseDragged:))] #[method(rightMouseDragged:)]
fn right_mouse_dragged(&self, event: &NSEvent) { fn right_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(otherMouseDragged:))] #[method(otherMouseDragged:)]
fn other_mouse_dragged(&self, event: &NSEvent) { fn other_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(mouseEntered:))] #[method(mouseEntered:)]
fn mouse_entered(&self, event: &NSEvent) { fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:"); trace_scope!("mouseEntered:");
@@ -646,7 +658,7 @@ define_class!(
}); });
} }
#[unsafe(method(mouseExited:))] #[method(mouseExited:)]
fn mouse_exited(&self, event: &NSEvent) { fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:"); trace_scope!("mouseExited:");
@@ -660,7 +672,7 @@ define_class!(
}); });
} }
#[unsafe(method(scrollWheel:))] #[method(scrollWheel:)]
fn scroll_wheel(&self, event: &NSEvent) { fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:"); trace_scope!("scrollWheel:");
@@ -677,9 +689,9 @@ define_class!(
}; };
// The "momentum phase," if any, has higher priority than touch phase (the two should // The "momentum phase," if any, has higher priority than touch phase (the two should
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no // be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum
// momentum phase is recorded (or rather, the started/ended cases of the // phase is recorded (or rather, the started/ended cases of the momentum phase) then we
// momentum phase) then we report the touch phase. // report the touch phase.
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } { let phase = match unsafe { event.momentumPhase() } {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
@@ -693,13 +705,17 @@ define_class!(
self.update_modifiers(event, false); self.update_modifiers(event, false);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta }) app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
);
self.queue_event(WindowEvent::MouseWheel {
device_id: None,
delta,
phase,
}); });
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
} }
#[unsafe(method(magnifyWithEvent:))] #[method(magnifyWithEvent:)]
fn magnify_with_event(&self, event: &NSEvent) { fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:"); trace_scope!("magnifyWithEvent:");
@@ -721,16 +737,18 @@ define_class!(
}); });
} }
#[unsafe(method(smartMagnifyWithEvent:))] #[method(smartMagnifyWithEvent:)]
fn smart_magnify_with_event(&self, event: &NSEvent) { fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:"); trace_scope!("smartMagnifyWithEvent:");
self.mouse_motion(event); self.mouse_motion(event);
self.queue_event(WindowEvent::DoubleTapGesture { device_id: None }); self.queue_event(WindowEvent::DoubleTapGesture {
device_id: None,
});
} }
#[unsafe(method(rotateWithEvent:))] #[method(rotateWithEvent:)]
fn rotate_with_event(&self, event: &NSEvent) { fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:"); trace_scope!("rotateWithEvent:");
@@ -752,7 +770,7 @@ define_class!(
}); });
} }
#[unsafe(method(pressureChangeWithEvent:))] #[method(pressureChangeWithEvent:)]
fn pressure_change_with_event(&self, event: &NSEvent) { fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:"); trace_scope!("pressureChangeWithEvent:");
@@ -766,13 +784,13 @@ define_class!(
// Allows us to receive Ctrl-Tab and Ctrl-Esc. // Allows us to receive Ctrl-Tab and Ctrl-Esc.
// Note that this *doesn't* help with any missing Cmd inputs. // Note that this *doesn't* help with any missing Cmd inputs.
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
#[unsafe(method(_wantsKeyDownForEvent:))] #[method(_wantsKeyDownForEvent:)]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:"); trace_scope!("_wantsKeyDownForEvent:");
true true
} }
#[unsafe(method(acceptsFirstMouse:))] #[method(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:"); trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse self.ivars().accepts_first_mouse
@@ -803,19 +821,26 @@ impl WinitView {
accepts_first_mouse, accepts_first_mouse,
option_as_alt: Cell::new(option_as_alt), option_as_alt: Cell::new(option_as_alt),
}); });
let this: Retained<Self> = unsafe { msg_send![super(this), init] }; let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source(); *this.ivars().input_source.borrow_mut() = this.current_input_source();
this this
} }
fn window(&self) -> Retained<NSWindow> { fn window(&self) -> Retained<WinitWindow> {
(**self).window().expect("view must be installed in a window") let window = (**self).window().expect("view must be installed in a window");
if !window.isKindOfClass(WinitWindow::class()) {
unreachable!("view installed in non-WinitWindow");
}
// SAFETY: Just checked that the window is `WinitWindow`
unsafe { Retained::cast(window) }
} }
fn queue_event(&self, event: WindowEvent) { fn queue_event(&self, event: WindowEvent) {
let window_id = window_id(&self.window()); let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event); app.window_event(event_loop, window_id, event);
}); });
@@ -940,8 +965,10 @@ impl WinitView {
// We'll correct this later. // We'll correct this later.
state: Pressed, state: Pressed,
text: None, text: None,
text_with_all_modifiers: None, platform_specific: KeyEventExtra {
key_without_modifiers: logical_key.clone(), text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
}; };
let location_mask = ModLocationMask::from_location(event.location); let location_mask = ModLocationMask::from_location(event.location);
@@ -1103,7 +1130,7 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEven
OptionAsAlt::Both if ev_mods.alt_key() => true, OptionAsAlt::Both if ev_mods.alt_key() => true,
_ => false, _ => false,
} && !ev_mods.control_key() } && !ev_mods.control_key()
&& !ev_mods.meta_key(); && !ev_mods.super_key();
if ignore_alt_characters { if ignore_alt_characters {
let ns_chars = unsafe { let ns_chars = unsafe {

View File

@@ -1,26 +1,22 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::sync::Arc;
use dispatch2::MainThreadBound;
use dpi::{Position, Size}; use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained}; use objc2::rc::{autoreleasepool, Retained};
use objc2::{define_class, MainThreadMarker, Message}; use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow}; use objc2_app_kit::{NSResponder, NSWindow};
use objc2_foundation::NSObject; use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop; use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate; use super::window_delegate::WindowDelegate;
use crate::error::RequestError; use crate::error::RequestError;
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::window::{ use crate::window::{
Cursor, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow,
WindowButtons, WindowId, WindowLevel, WindowAttributes, WindowButtons, WindowId, WindowLevel,
}; };
#[derive(Debug)]
pub(crate) struct Window { pub(crate) struct Window {
window: MainThreadBound<Retained<NSWindow>>, window: MainThreadBound<Retained<WinitWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here. /// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>, delegate: MainThreadBound<Retained<WindowDelegate>>,
} }
@@ -68,7 +64,7 @@ impl Window {
impl Drop for Window { impl Drop for Window {
fn drop(&mut self) { fn drop(&mut self) {
// Restore the video mode. // Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_, _))) { if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
self.set_fullscreen(None); self.set_fullscreen(None);
} }
@@ -208,11 +204,11 @@ impl CoreWindow for Window {
} }
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) { fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen)) self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
} }
fn fullscreen(&self) -> Option<Fullscreen> { fn fullscreen(&self) -> Option<Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen()) self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
} }
fn set_decorations(&self, decorations: bool) { fn set_decorations(&self, decorations: bool) {
@@ -309,24 +305,21 @@ impl CoreWindow for Window {
fn current_monitor(&self) -> Option<CoreMonitorHandle> { fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| { self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor))) delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
}) })
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| { self.maybe_wait_on_main(|delegate| {
Box::new( Box::new(
delegate delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
.available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
) )
}) })
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| { self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor))) delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
}) })
} }
@@ -339,21 +332,27 @@ impl CoreWindow for Window {
} }
} }
define_class!( declare_class!(
#[unsafe(super(NSWindow, NSResponder, NSObject))]
#[name = "WinitWindow"]
#[derive(Debug)] #[derive(Debug)]
pub struct WinitWindow; pub struct WinitWindow;
/// This documentation attribute makes rustfmt work for some reason? unsafe impl ClassType for WinitWindow {
impl WinitWindow { #[inherits(NSResponder, NSObject)]
#[unsafe(method(canBecomeMainWindow))] type Super = NSWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
impl DeclaredClass for WinitWindow {}
unsafe impl WinitWindow {
#[method(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool { fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow"); trace_scope!("canBecomeMainWindow");
true true
} }
#[unsafe(method(canBecomeKeyWindow))] #[method(canBecomeKeyWindow)]
fn can_become_key_window(&self) -> bool { fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow"); trace_scope!("canBecomeKeyWindow");
true true
@@ -361,24 +360,8 @@ define_class!(
} }
); );
define_class!( impl WinitWindow {
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))] pub(super) fn id(&self) -> WindowId {
#[name = "WinitPanel"] WindowId::from_raw(self as *const Self as usize)
#[derive(Debug)]
pub struct WinitPanel;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitPanel {
// although NSPanel can become key window
// it doesn't if window doesn't have NSWindowStyleMask::Titled
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
}
} }
);
pub(super) fn window_id(window: &NSWindow) -> WindowId {
WindowId::from_raw(window as *const _ as usize)
} }

File diff suppressed because it is too large Load Diff

View File

@@ -8,10 +8,10 @@ use crate::application::ApplicationHandler;
#[derive(Default)] #[derive(Default)]
pub(crate) struct EventHandler { pub(crate) struct EventHandler {
/// This can be in the following states: /// This can be in the following states:
/// - Not registered by the event loop, or terminated (None). /// - Not registered by the event loop (None).
/// - Present (Some(handler)). /// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed). /// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<Box<dyn ApplicationHandler + 'static>>>, inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
} }
impl fmt::Debug for EventHandler { impl fmt::Debug for EventHandler {
@@ -37,7 +37,7 @@ impl EventHandler {
/// from within the closure. /// from within the closure.
pub(crate) fn set<'handler, R>( pub(crate) fn set<'handler, R>(
&self, &self,
app: Box<dyn ApplicationHandler + 'handler>, app: &'handler mut dyn ApplicationHandler,
closure: impl FnOnce() -> R, closure: impl FnOnce() -> R,
) -> R { ) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can // SAFETY: We extend the lifetime of the handler here so that we can
@@ -48,8 +48,8 @@ impl EventHandler {
// extended beyond `'handler`. // extended beyond `'handler`.
let handler = unsafe { let handler = unsafe {
mem::transmute::< mem::transmute::<
Box<dyn ApplicationHandler + 'handler>, &'handler mut dyn ApplicationHandler,
Box<dyn ApplicationHandler + 'static>, &'static mut dyn ApplicationHandler,
>(app) >(app)
}; };
@@ -71,13 +71,10 @@ impl EventHandler {
fn drop(&mut self) { fn drop(&mut self) {
match self.0.inner.try_borrow_mut().as_deref_mut() { match self.0.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => { Ok(data @ Some(_)) => {
let handler = data.take(); *data = None;
// Explicitly `Drop` the application handler.
drop(handler);
}, },
Ok(None) => { Ok(None) => {
// Allowed, happens if the handler was cleared manually tracing::error!("tried to clear handler, but no handler was set");
// elsewhere (such as in `applicationWillTerminate:`).
}, },
Err(_) => { Err(_) => {
// Note: This is not expected to ever happen, this // Note: This is not expected to ever happen, this
@@ -113,16 +110,16 @@ impl EventHandler {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_))) matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
} }
pub(crate) fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) { pub(crate) fn handle(&self, callback: impl FnOnce(&mut dyn ApplicationHandler)) {
match self.inner.try_borrow_mut().as_deref_mut() { match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(ref mut user_app)) => { Ok(Some(user_app)) => {
// It is important that we keep the reference borrowed here, // It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is // so that `in_use` can properly detect that the handler is
// still in use. // still in use.
// //
// If the handler unwinds, the `RefMut` will ensure that the // If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed. // handler is no longer borrowed.
callback(&mut **user_app); callback(*user_app);
}, },
Ok(None) => { Ok(None) => {
// `NSApplication`, our app state and this handler are all // `NSApplication`, our app state and this handler are all
@@ -136,21 +133,4 @@ impl EventHandler {
}, },
} }
} }
pub(crate) fn terminate(&self) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
let handler = data.take();
// Explicitly `Drop` the application handler.
drop(handler);
},
Ok(None) => {
// When terminating, we expect the application handler to still be registered.
tracing::error!("tried to clear handler, but no handler was set");
},
Err(_) => {
panic!("tried to clear handler while an event is currently being handled");
},
}
}
} }

View File

@@ -1,124 +0,0 @@
use std::os::raw::c_void;
use std::sync::Arc;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext,
};
use crate::event_loop::EventLoopProxyProvider;
/// A waker that signals a `CFRunLoopSource` on the main thread.
///
/// We use this to integrate with the system as cleanly as possible (instead of e.g. keeping an
/// atomic around that we check on each iteration of the event loop).
///
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct EventLoopProxy {
source: CFRetained<CFRunLoopSource>,
/// Cached value of `CFRunLoopGetMain`.
main_loop: CFRetained<CFRunLoop>,
}
// FIXME(madsmtm): Mark `CFRunLoopSource` + `CFRunLoop` as `Send` + `Sync`.
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl EventLoopProxy {
/// Create a new proxy, registering it to be performed on the main thread.
///
/// The provided closure should call `proxy_wake_up` on the application.
pub(crate) fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self {
// We use an `Arc` here to make sure that the reference-counting of the signal container is
// atomic (`Retained`/`CFRetained` would be valid alternatives too).
let signaller = Arc::new(signaller);
unsafe extern "C-unwind" fn retain<F>(info: *const c_void) -> *const c_void {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
unsafe { Arc::increment_strong_count(info.cast::<F>()) };
info
}
unsafe extern "C-unwind" fn release<F>(info: *const c_void) {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
unsafe { Arc::decrement_strong_count(info.cast::<F>()) };
}
// Pointer equality / hashing.
extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 {
(info1 == info2) as u8
}
extern "C-unwind" fn hash(info: *const c_void) -> usize {
info as usize
}
// Call the provided closure.
unsafe extern "C-unwind" fn perform<F: Fn()>(info: *mut c_void) {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
let signaller = unsafe { &*info.cast::<F>() };
(signaller)();
}
// Fire last.
let order = CFIndex::MAX - 1;
// This is marked `mut` to match the signature of `CFRunLoopSourceCreate`, but the
// information is copied, and not actually mutated.
let mut context = CFRunLoopSourceContext {
version: 0,
// This is retained on creation.
info: Arc::as_ptr(&signaller) as *mut c_void,
retain: Some(retain::<F>),
release: Some(release::<F>),
copyDescription: None,
equal: Some(equal),
hash: Some(hash),
schedule: None,
cancel: None,
perform: Some(perform::<F>),
};
// SAFETY: The normal callbacks are thread-safe (`retain`/`release` use atomics, and
// `equal`/`hash` only access a pointer).
//
// Note that the `perform` callback isn't thread-safe (we don't have `F: Send + Sync`), but
// that's okay, since we are on the main thread, and the source is only added to the main
// run loop (below), and hence only performed there.
//
// Keeping the closure alive beyond this scope is fine, because `F: 'static`.
let source = unsafe {
let _ = mtm;
CFRunLoopSource::new(None, order, &mut context).unwrap()
};
// Register the source to be performed on the main thread.
let main_loop = CFRunLoop::main().unwrap();
unsafe { main_loop.add_source(Some(&source), kCFRunLoopCommonModes) };
Self { source, main_loop }
}
// FIXME(madsmtm): Use this on macOS too.
// More difficult there, since the user can re-start the event loop.
#[cfg_attr(target_os = "macos", allow(dead_code))]
pub(crate) fn invalidate(&self) {
// NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that
// we only need to register a single source even if there's multiple proxies in use.
self.source.invalidate();
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
// Signal the source, which ends up later invoking `perform` on the main thread.
//
// Multiple signals in quick succession are automatically coalesced into a single signal.
self.source.signal();
// Let the main thread know there's a new event.
//
// This is required since we may be (probably are) running on a different thread, and the
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
self.main_loop.wake_up();
}
}

View File

@@ -3,7 +3,6 @@
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod appkit; mod appkit;
mod event_handler; mod event_handler;
mod event_loop_proxy;
mod notification_center; mod notification_center;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
mod uikit; mod uikit;

View File

@@ -2,10 +2,7 @@ use std::ptr::NonNull;
use block2::RcBlock; use block2::RcBlock;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::ProtocolObject; use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
use objc2_foundation::{
NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol,
};
/// Observe the given notification. /// Observe the given notification.
/// ///
@@ -15,7 +12,7 @@ pub fn create_observer(
center: &NSNotificationCenter, center: &NSNotificationCenter,
name: &NSNotificationName, name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static, handler: impl Fn(&NSNotification) + 'static,
) -> Retained<ProtocolObject<dyn NSObjectProtocol>> { ) -> Retained<NSObject> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| { let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() }); handler(unsafe { notification.as_ref() });
}); });

View File

@@ -3,23 +3,28 @@
use std::cell::{OnceCell, RefCell, RefMut}; use std::cell::{OnceCell, RefCell, RefMut};
use std::collections::HashSet; use std::collections::HashSet;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::sync::{Arc, Mutex}; use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant; use std::time::Instant;
use std::{mem, ptr}; use std::{mem, ptr};
use dispatch2::MainThreadBound; use core_foundation::base::CFRelease;
use objc2::rc::Retained; use core_foundation::date::CFAbsoluteTimeGetCurrent;
use objc2::MainThreadMarker; use core_foundation::runloop::{
use objc2_core_foundation::{ kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, CGRect, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
CGSize,
}; };
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView}; use objc2::rc::Retained;
use objc2::sel;
use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::super::event_handler::EventHandler; use super::super::event_handler::EventHandler;
use super::super::event_loop_proxy::EventLoopProxy;
use super::window::WinitUIWindow; use super::window::WinitUIWindow;
use super::ActiveEventLoop; use super::{ActiveEventLoop, EventLoopProxy};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize; use crate::dpi::PhysicalSize;
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent}; use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
@@ -43,10 +48,22 @@ macro_rules! bug_assert {
/// This is stored separately from AppState, since AppState needs to be accessible while the handler /// This is stored separately from AppState, since AppState needs to be accessible while the handler
/// is executing. /// is executing.
fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler { fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept // SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept
// of the main thread. // of the main thread.
static GLOBAL: MainThreadBound<OnceCell<EventHandler>> = static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() }); StaticMainThreadBound(OnceCell::new());
GLOBAL.get(mtm).get_or_init(EventHandler::new) GLOBAL.get(mtm).get_or_init(EventHandler::new)
} }
@@ -101,7 +118,7 @@ pub(crate) struct AppState {
} }
impl AppState { impl AppState {
pub(crate) fn get_mut(mtm: MainThreadMarker) -> RefMut<'static, AppState> { pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the // basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs. // std::sync APIs.
// must be mut because plain `static` requires `Sync` // must be mut because plain `static` requires `Sync`
@@ -113,21 +130,17 @@ impl AppState {
if guard.is_none() { if guard.is_none() {
#[inline(never)] #[inline(never)]
#[cold] #[cold]
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>, mtm: MainThreadMarker) { fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(CFRunLoop::main().unwrap()); let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}));
**guard = Some(AppState { **guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }), app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(), control_flow: ControlFlow::default(),
waker, waker,
event_loop_proxy, event_loop_proxy: Arc::new(EventLoopProxy::new()),
queued_events: Vec::new(), queued_events: Vec::new(),
}); });
} }
init_guard(&mut guard, mtm); init_guard(&mut guard);
} }
RefMut::map(guard, |state| state.as_mut().unwrap()) RefMut::map(guard, |state| state.as_mut().unwrap())
} }
@@ -242,7 +255,6 @@ impl AppState {
(ControlFlow::Wait, ControlFlow::Wait) => { (ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start }); self.set_state(AppStateImpl::Waiting { start });
self.waker.stop()
}, },
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant => if old_instant == new_instant =>
@@ -297,19 +309,15 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<W
}, },
s @ &mut AppStateImpl::ProcessingRedraws { .. } s @ &mut AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished => bug!("unexpected state {:?}", s), | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => { &mut AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated") panic!("Attempt to create a `Window` after the app has terminated")
}, },
} }
} }
pub(crate) fn launch<R>( pub(crate) fn launch(mtm: MainThreadMarker, app: &mut dyn ApplicationHandler, run: impl FnOnce()) {
mtm: MainThreadMarker, get_handler(mtm).set(app, run)
app: impl ApplicationHandler,
run: impl FnOnce() -> R,
) -> R {
get_handler(mtm).set(Box::new(app), run)
} }
pub fn did_finish_launching(mtm: MainThreadMarker) { pub fn did_finish_launching(mtm: MainThreadMarker) {
@@ -401,8 +409,13 @@ fn handle_user_events(mtm: MainThreadMarker) {
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) { if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
bug!("user events attempted to be sent out while `ProcessingRedraws`"); bug!("user events attempted to be sent out while `ProcessingRedraws`");
} }
let event_loop_proxy = this.event_loop_proxy().clone();
drop(this); drop(this);
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let queued_events = mem::take(&mut this.queued_events); let queued_events = mem::take(&mut this.queued_events);
@@ -414,6 +427,10 @@ fn handle_user_events(mtm: MainThreadMarker) {
for event in queued_events { for event in queued_events {
handle_wrapped_event(mtm, event); handle_wrapped_event(mtm, event);
} }
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}
} }
} }
@@ -423,7 +440,13 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
let mut events = Vec::new(); let mut events = Vec::new();
#[allow(deprecated)] #[allow(deprecated)]
for window in application.windows().iter() { for window in application.windows().iter() {
if let Ok(window) = window.downcast::<WinitUIWindow>() { if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::Window { events.push(EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::Occluded(occluded), event: WindowEvent::Occluded(occluded),
@@ -487,7 +510,13 @@ pub(crate) fn terminated(application: &UIApplication) {
let mut events = Vec::new(); let mut events = Vec::new();
#[allow(deprecated)] #[allow(deprecated)]
for window in application.windows().iter() { for window in application.windows().iter() {
if let Ok(window) = window.downcast::<WinitUIWindow>() { if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::Window { events.push(EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::Destroyed, event: WindowEvent::Destroyed,
@@ -498,11 +527,9 @@ pub(crate) fn terminated(application: &UIApplication) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
this.terminated_transition(); this.terminated_transition();
// Prevent EventLoopProxy from firing again.
this.event_loop_proxy.invalidate();
drop(this); drop(this);
get_handler(mtm).terminate(); get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
} }
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) { fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
@@ -542,44 +569,46 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
} }
struct EventLoopWaker { struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>, timer: CFRunLoopTimerRef,
} }
impl Drop for EventLoopWaker { impl Drop for EventLoopWaker {
fn drop(&mut self) { fn drop(&mut self) {
self.timer.invalidate(); unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
} }
} }
impl EventLoopWaker { impl EventLoopWaker {
fn new(rl: CFRetained<CFRunLoop>) -> EventLoopWaker { fn new(rl: CFRunLoopRef) -> EventLoopWaker {
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {} extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe { unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the // It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching // future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimer::new( let timer = CFRunLoopTimerCreate(
None, ptr::null_mut(),
f64::MAX, f64::MAX,
0.000_000_1, 0.000_000_1,
0, 0,
0, 0,
Some(wakeup_main_loop), wakeup_main_loop,
ptr::null_mut(), ptr::null_mut(),
) );
.unwrap(); CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
rl.add_timer(Some(&timer), kCFRunLoopCommonModes);
EventLoopWaker { timer } EventLoopWaker { timer }
} }
} }
fn stop(&mut self) { fn stop(&mut self) {
self.timer.set_next_fire_date(f64::MAX); unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
} }
fn start(&mut self) { fn start(&mut self) {
self.timer.set_next_fire_date(f64::MIN); unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
} }
fn start_at(&mut self, instant: Instant) { fn start_at(&mut self, instant: Instant) {
@@ -587,11 +616,99 @@ impl EventLoopWaker {
if now >= instant { if now >= instant {
self.start(); self.start();
} else { } else {
let current = CFAbsoluteTimeGetCurrent(); unsafe {
let duration = instant - now; let current = CFAbsoluteTimeGetCurrent();
let fsecs = let duration = instant - now;
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; let fsecs =
self.timer.set_next_fire_date(current + fsecs); duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
} }
} }
} }
macro_rules! os_capabilities {
(
$(
$(#[$attr:meta])*
$error_name:ident: $objc_call:literal,
$name:ident: $major:literal-$minor:literal
),*
$(,)*
) => {
#[derive(Clone, Debug)]
pub struct OSCapabilities {
$(
pub $name: bool,
)*
os_version: NSOperatingSystemVersion,
}
impl OSCapabilities {
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
$(let $name = meets_requirements(os_version, $major, $minor);)*
Self { $($name,)* os_version, }
}
}
impl OSCapabilities {$(
$(#[$attr])*
pub fn $error_name(&self, extra_msg: &str) {
tracing::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
)
}
)*}
};
}
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,
}
fn meets_requirements(
version: NSOperatingSystemVersion,
required_major: NSInteger,
required_minor: NSInteger,
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
fn get_version() -> NSOperatingSystemVersion {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8 = 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, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
}

View File

@@ -1,19 +1,24 @@
use std::ffi::c_void; use std::ffi::{c_char, c_int, c_void};
use std::ptr; use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc; use std::sync::Arc;
use objc2::rc::Retained; use core_foundation::base::{CFIndex, CFRelease};
use objc2::runtime::ProtocolObject; use core_foundation::runloop::{
use objc2::{msg_send, ClassType, MainThreadMarker}; kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
use objc2_core_foundation::{ kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
kCFRunLoopDefaultMode, CFIndex, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
}; };
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
use objc2_ui_kit::{ use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification, UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification, UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationWillEnterForegroundNotification, UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen, UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
UIApplicationWillTerminateNotification, UIScreen,
}; };
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
@@ -24,9 +29,10 @@ use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError}; use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event_loop::{ use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
}; };
use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform_impl::Window; use crate::platform_impl::Window;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as CoreWindow}; use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as CoreWindow};
@@ -54,18 +60,14 @@ impl RootActiveEventLoop for ActiveEventLoop {
Err(NotSupportedError::new("create_custom_cursor is not supported").into()) Err(NotSupportedError::new("create_custom_cursor is not supported").into())
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new( Box::new(monitor::uiscreens(self.mtm).into_iter().map(|inner| RootMonitorHandle { inner }))
monitor::uiscreens(self.mtm)
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
} }
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> { fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
#[allow(deprecated)] #[allow(deprecated)]
let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm)); let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm));
Some(CoreMonitorHandle(Arc::new(monitor))) Some(RootMonitorHandle { inner: monitor })
} }
fn listen_device_events(&self, _allowed: DeviceEvents) {} fn listen_device_events(&self, _allowed: DeviceEvents) {}
@@ -118,7 +120,6 @@ impl HasDisplayHandle for OwnedDisplayHandle {
} }
} }
#[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
mtm: MainThreadMarker, mtm: MainThreadMarker,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
@@ -127,13 +128,13 @@ pub struct EventLoop {
// system instead cleans it up next time it would have posted a notification to it. // system instead cleans it up next time it would have posted a notification to it.
// //
// Though we do still need to keep the observers around to prevent them from being deallocated. // Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_finish_launching_observer: Retained<NSObject>,
_did_become_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_become_active_observer: Retained<NSObject>,
_will_resign_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _will_resign_active_observer: Retained<NSObject>,
_will_enter_foreground_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _will_enter_foreground_observer: Retained<NSObject>,
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_enter_background_observer: Retained<NSObject>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _will_terminate_observer: Retained<NSObject>,
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_receive_memory_warning_observer: Retained<NSObject>,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -188,9 +189,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect( let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object", "UIApplicationWillEnterForegroundNotification to have application object",
); );
// The `object` in `UIApplicationWillEnterForegroundNotification` is documented to // SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
// be `UIApplication`. // documented to be `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap(); let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, false); send_occluded_event_for_all_windows(&app, false);
}, },
); );
@@ -202,9 +203,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect( let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object", "UIApplicationDidEnterBackgroundNotification to have application object",
); );
// The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be // SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
// `UIApplication`. // documented to be `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap(); let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, true); send_occluded_event_for_all_windows(&app, true);
}, },
); );
@@ -215,9 +216,9 @@ impl EventLoop {
move |notification| { move |notification| {
let app = unsafe { notification.object() } let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object"); .expect("UIApplicationWillTerminateNotification to have application object");
// The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented // SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
// to be `UIApplication`. // (somewhat) documented to be `UIApplication`.
let app = app.downcast::<UIApplication>().unwrap(); let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
app_state::terminated(&app); app_state::terminated(&app);
}, },
); );
@@ -241,9 +242,9 @@ impl EventLoop {
}) })
} }
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! { pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
let application: Option<Retained<UIApplication>> = let application: Option<Retained<UIApplication>> =
unsafe { msg_send![UIApplication::class(), sharedApplication] }; unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!( assert!(
application.is_none(), application.is_none(),
"\ "\
@@ -251,9 +252,24 @@ impl EventLoop {
`EventLoop::run_app` calls `UIApplicationMain` on iOS", `EventLoop::run_app` calls `UIApplicationMain` on iOS",
); );
// We intentionally override neither the application nor the delegate, extern "C" {
// to allow the user to do so themselves! // These functions are in crt_externs.h.
app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm)) fn _NSGetArgc() -> *mut c_int;
fn _NSGetArgv() -> *mut *mut *mut c_char;
}
app_state::launch(self.mtm, &mut app, || unsafe {
UIApplicationMain(
*_NSGetArgc(),
NonNull::new(*_NSGetArgv()).unwrap(),
// We intentionally override neither the application nor the delegate, to allow
// the user to do so themselves!
None,
None,
);
});
unreachable!()
} }
pub fn window_target(&self) -> &dyn RootActiveEventLoop { pub fn window_target(&self) -> &dyn RootActiveEventLoop {
@@ -261,19 +277,78 @@ impl EventLoop {
} }
} }
pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
}
}
}
impl EventLoopProxy {
pub(crate) fn new() -> EventLoopProxy {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}
fn setup_control_flow_observers() { fn setup_control_flow_observers() {
unsafe { unsafe {
// begin is queued with the highest priority to ensure it is processed before other // begin is queued with the highest priority to ensure it is processed before other
// observers // observers
extern "C-unwind" fn control_flow_begin_handler( extern "C" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver, _: CFRunLoopObserverRef,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm), kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -289,68 +364,65 @@ fn setup_control_flow_observers() {
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
// //
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C-unwind" fn control_flow_main_end_handler( extern "C" fn control_flow_main_end_handler(
_: *mut CFRunLoopObserver, _: CFRunLoopObserverRef,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm), kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(), _ => unreachable!(),
} }
} }
// end is queued with the lowest priority to ensure it is processed after other observers // end is queued with the lowest priority to ensure it is processed after other observers
extern "C-unwind" fn control_flow_end_handler( extern "C" fn control_flow_end_handler(
_: *mut CFRunLoopObserver, _: CFRunLoopObserverRef,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm), kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(), _ => unreachable!(),
} }
} }
let main_loop = CFRunLoop::main().unwrap(); let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserver::new( let begin_observer = CFRunLoopObserverCreate(
None, ptr::null_mut(),
CFRunLoopActivity::AfterWaiting.0, kCFRunLoopAfterWaiting,
true, 1, // repeat = true
CFIndex::MIN, CFIndex::MIN,
Some(control_flow_begin_handler), control_flow_begin_handler,
ptr::null_mut(), ptr::null_mut(),
) );
.unwrap(); CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
main_loop.add_observer(Some(&begin_observer), kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserver::new( let main_end_observer = CFRunLoopObserverCreate(
None, ptr::null_mut(),
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0, kCFRunLoopExit | kCFRunLoopBeforeWaiting,
true, 1, // repeat = true
0, // see comment on `control_flow_main_end_handler` 0, // see comment on `control_flow_main_end_handler`
Some(control_flow_main_end_handler), control_flow_main_end_handler,
ptr::null_mut(), ptr::null_mut(),
) );
.unwrap(); CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
main_loop.add_observer(Some(&main_end_observer), kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserver::new( let end_observer = CFRunLoopObserverCreate(
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
CFIndex::MAX,
Some(control_flow_end_handler),
ptr::null_mut(), ptr::null_mut(),
) kCFRunLoopExit | kCFRunLoopBeforeWaiting,
.unwrap(); 1, // repeat = true
main_loop.add_observer(Some(&end_observer), kCFRunLoopDefaultMode); CFIndex::MAX,
control_flow_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
} }
} }

View File

@@ -10,10 +10,18 @@ mod window;
use std::fmt; use std::fmt;
pub(crate) use self::event_loop::{ pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes, ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
}; };
pub(crate) use self::monitor::MonitorHandle; pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window}; pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}
#[derive(Debug)] #[derive(Debug)]
pub enum OsError {} pub enum OsError {}

View File

@@ -1,29 +1,30 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque; use std::collections::{BTreeSet, VecDeque};
use std::num::NonZeroU32; use std::num::{NonZeroU16, NonZeroU32};
use std::{fmt, hash, ptr}; use std::{fmt, hash, ptr};
use dispatch2::{run_on_main, MainThreadBound}; use objc2::mutability::IsRetainable;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::{available, MainThreadMarker, Message}; use objc2::Message;
use objc2_foundation::NSInteger; use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2_ui_kit::{UIScreen, UIScreenMode}; use objc2_ui_kit::{UIScreen, UIScreenMode};
use crate::dpi::PhysicalPosition; use super::app_state;
use crate::monitor::{MonitorHandleProvider, VideoMode}; use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
// Workaround for `MainThreadBound` implementing almost no traits // Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)] #[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>); struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
impl<T: Message> Clone for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm))) Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm)))
} }
} }
impl<T: Message> hash::Hash for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) { fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -31,7 +32,7 @@ impl<T: Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: Message> PartialEq for MainThreadBoundDelegateImpls<T> { impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -39,12 +40,14 @@ impl<T: Message> PartialEq for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: Message> Eq for MainThreadBoundDelegateImpls<T> {} impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
pub(crate) mode: VideoMode, pub(crate) size: (u32, u32),
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>, screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
pub(crate) monitor: MonitorHandle,
} }
impl VideoModeHandle { impl VideoModeHandle {
@@ -55,18 +58,30 @@ impl VideoModeHandle {
) -> VideoModeHandle { ) -> VideoModeHandle {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen); let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size(); let size = screen_mode.size();
let mode = VideoMode {
size: (size.width as u32, size.height as u32).into(),
bit_depth: None,
refresh_rate_millihertz,
};
VideoModeHandle { VideoModeHandle {
mode, size: (size.width as u32, size.height as u32),
refresh_rate_millihertz,
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)), screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
monitor: MonitorHandle::new(uiscreen),
} }
} }
pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
None
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained<UIScreenMode> { pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained<UIScreenMode> {
self.screen_mode.0.get(mtm) self.screen_mode.0.get(mtm)
} }
@@ -76,60 +91,6 @@ pub struct MonitorHandle {
ui_screen: MainThreadBound<Retained<UIScreen>>, ui_screen: MainThreadBound<Retained<UIScreen>>,
} }
impl MonitorHandleProvider for MonitorHandle {
fn id(&self) -> u128 {
self.native_id() as _
}
fn native_id(&self) -> u64 {
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)) as u64
}
fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
run_on_main(|mtm| {
#[allow(deprecated)]
let main = UIScreen::mainScreen(mtm);
if *self.ui_screen(mtm) == main {
Some("Primary".into())
} else if Some(self.ui_screen(mtm)) == main.mirroredScreen().as_ref() {
Some("Mirrored".into())
} else {
#[allow(deprecated)]
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == *self.ui_screen(mtm))
.map(|idx| idx.to_string().into())
}
})
}
fn position(&self) -> Option<PhysicalPosition<i32>> {
let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
Some((bounds.origin.x as f64, bounds.origin.y as f64).into())
}
fn scale_factor(&self) -> f64 {
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
}
fn current_video_mode(&self) -> Option<VideoMode> {
Some(run_on_main(|mtm| {
VideoModeHandle::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).currentMode().unwrap(),
mtm,
)
.mode
}))
}
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
Box::new(self.video_modes())
}
}
impl Clone for MonitorHandle { impl Clone for MonitorHandle {
fn clone(&self) -> Self { fn clone(&self) -> Self {
run_on_main(|mtm| Self { run_on_main(|mtm| Self {
@@ -191,42 +152,79 @@ impl MonitorHandle {
Self { ui_screen: MainThreadBound::new(ui_screen, mtm) } Self { ui_screen: MainThreadBound::new(ui_screen, mtm) }
} }
pub fn video_modes_handles(&self) -> impl Iterator<Item = VideoModeHandle> { pub fn name(&self) -> Option<String> {
run_on_main(|mtm| { run_on_main(|mtm| {
let ui_screen = self.ui_screen(mtm); #[allow(deprecated)]
let main = UIScreen::mainScreen(mtm);
ui_screen if *self.ui_screen(mtm) == main {
.availableModes() Some("Primary".to_string())
.into_iter() } else if Some(self.ui_screen(mtm)) == main.mirroredScreen().as_ref() {
.map(|mode| VideoModeHandle::new(ui_screen.clone(), mode, mtm)) Some("Mirrored".to_string())
.collect::<Vec<_>>() } else {
.into_iter() #[allow(deprecated)]
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == &**self.ui_screen(mtm))
.map(|idx| idx.to_string())
}
}) })
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn position(&self) -> Option<PhysicalPosition<i32>> {
self.video_modes_handles().map(|handle| handle.mode) let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
Some((bounds.origin.x as f64, bounds.origin.y as f64).into())
}
pub fn scale_factor(&self) -> f64 {
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
Some(run_on_main(|mtm| {
VideoModeHandle::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).currentMode().unwrap(),
mtm,
)
}))
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
run_on_main(|mtm| {
let ui_screen = self.ui_screen(mtm);
// Use Ord impl of RootVideoModeHandle
let modes: BTreeSet<_> = ui_screen
.availableModes()
.into_iter()
.map(|mode| RootVideoModeHandle {
video_mode: VideoModeHandle::new(ui_screen.clone(), mode, mtm),
})
.collect();
modes.into_iter().map(|mode| mode.video_mode)
})
} }
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> { pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> {
self.ui_screen.get(mtm) self.ui_screen.get(mtm)
} }
pub fn preferred_video_mode(&self) -> VideoMode { pub fn preferred_video_mode(&self) -> VideoModeHandle {
run_on_main(|mtm| { run_on_main(|mtm| {
VideoModeHandle::new( VideoModeHandle::new(
self.ui_screen(mtm).clone(), self.ui_screen(mtm).clone(),
self.ui_screen(mtm).preferredMode().unwrap(), self.ui_screen(mtm).preferredMode().unwrap(),
mtm, mtm,
) )
.mode
}) })
} }
} }
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> { fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
let refresh_rate_millihertz: NSInteger = { let refresh_rate_millihertz: NSInteger = {
if available!(ios = 10.3, tvos = 10.2) { let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
uiscreen.maximumFramesPerSecond() uiscreen.maximumFramesPerSecond()
} else { } else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html // https://developer.apple.com/library/archive/technotes/tn2460/_index.html
@@ -239,9 +237,7 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
// //
// FIXME: earlier OSs could calculate the refresh rate using // FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`. // `-[CADisplayLink duration]`.
tracing::warn!( os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
"`maximumFramesPerSecond` requires iOS 10.3+ or tvOS 10.2+. Defaulting to 60 fps"
);
60 60
} }
}; };
@@ -270,7 +266,7 @@ mod tests {
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm))); assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm); let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(&*screen, &*main))); assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
assert!(unsafe { assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm)) NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))

View File

@@ -3,9 +3,8 @@ use std::cell::{Cell, RefCell};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject}; use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{available, define_class, msg_send, sel, DefinedClass, MainThreadMarker}; use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_core_foundation::{CGFloat, CGPoint, CGRect}; use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
use objc2_foundation::{NSObject, NSSet, NSString};
use objc2_ui_kit::{ use objc2_ui_kit::{
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate, UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
@@ -22,6 +21,7 @@ use crate::event::{
WindowEvent, WindowEvent,
}; };
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey}; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::platform_impl::KeyEventExtra;
use crate::window::WindowAttributes; use crate::window::WindowAttributes;
pub struct WinitViewState { pub struct WinitViewState {
@@ -39,26 +39,36 @@ pub struct WinitViewState {
fingers: Cell<u8>, fingers: Cell<u8>,
} }
define_class!( declare_class!(
#[unsafe(super(UIView, UIResponder, NSObject))]
#[name = "WinitUIView"]
#[ivars = WinitViewState]
pub(crate) struct WinitView; pub(crate) struct WinitView;
/// This documentation attribute makes rustfmt work for some reason? unsafe impl ClassType for WinitView {
impl WinitView { #[inherits(UIResponder, NSObject)]
#[unsafe(method(drawRect:))] type Super = UIView;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIView";
}
impl DeclaredClass for WinitView {
type Ivars = WinitViewState;
}
unsafe impl WinitView {
#[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) { fn draw_rect(&self, rect: CGRect) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
let window = self.window().unwrap(); let window = self.window().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::Window { app_state::handle_nonuser_event(
window_id: window.id(), mtm,
event: WindowEvent::RedrawRequested, EventWrapper::Window {
}); window_id: window.id(),
event: WindowEvent::RedrawRequested,
},
);
let _: () = unsafe { msg_send![super(self), drawRect: rect] }; let _: () = unsafe { msg_send![super(self), drawRect: rect] };
} }
#[unsafe(method(layoutSubviews))] #[method(layoutSubviews)]
fn layout_subviews(&self) { fn layout_subviews(&self) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] }; let _: () = unsafe { msg_send![super(self), layoutSubviews] };
@@ -72,13 +82,16 @@ define_class!(
.to_physical(scale_factor); .to_physical(scale_factor);
let window = self.window().unwrap(); let window = self.window().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::Window { app_state::handle_nonuser_event(
window_id: window.id(), mtm,
event: WindowEvent::SurfaceResized(size), EventWrapper::Window {
}); window_id: window.id(),
event: WindowEvent::SurfaceResized(size),
},
);
} }
#[unsafe(method(setContentScaleFactor:))] #[method(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
let _: () = let _: () =
@@ -111,46 +124,49 @@ define_class!(
let window_id = window.id(); let window_id = window.id();
app_state::handle_nonuser_events( app_state::handle_nonuser_events(
mtm, mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged { std::iter::once(EventWrapper::ScaleFactorChanged(
window, app_state::ScaleFactorChanged {
scale_factor, window,
suggested_size: size.to_physical(scale_factor), scale_factor,
})) suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::Window { .chain(std::iter::once(EventWrapper::Window {
window_id, window_id,
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)), event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
})), },
)),
); );
} }
#[unsafe(method(safeAreaInsetsDidChange))] #[method(safeAreaInsetsDidChange)]
fn safe_area_changed(&self) { fn safe_area_changed(&self) {
debug!("safeAreaInsetsDidChange was called, requesting redraw"); debug!("safeAreaInsetsDidChange was called, requesting redraw");
// When the safe area changes we want to make sure to emit a redraw event // When the safe area changes we want to make sure to emit a redraw event
self.setNeedsDisplay(); self.setNeedsDisplay();
} }
#[unsafe(method(touchesBegan:withEvent:))] #[method(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[unsafe(method(touchesMoved:withEvent:))] #[method(touchesMoved:withEvent:)]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[unsafe(method(touchesEnded:withEvent:))] #[method(touchesEnded:withEvent:)]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[unsafe(method(touchesCancelled:withEvent:))] #[method(touchesCancelled:withEvent:)]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[unsafe(method(pinchGesture:))] #[method(pinchGesture:)]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) { fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
@@ -158,40 +174,46 @@ define_class!(
UIGestureRecognizerState::Began => { UIGestureRecognizerState::Began => {
self.ivars().pinch_last_delta.set(recognizer.scale()); self.ivars().pinch_last_delta.set(recognizer.scale());
(TouchPhase::Started, 0.0) (TouchPhase::Started, 0.0)
}, }
UIGestureRecognizerState::Changed => { UIGestureRecognizerState::Changed => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale()); let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale());
(TouchPhase::Moved, recognizer.scale() - last_scale) (TouchPhase::Moved, recognizer.scale() - last_scale)
}, }
UIGestureRecognizerState::Ended => { UIGestureRecognizerState::Ended => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0); let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0);
(TouchPhase::Moved, recognizer.scale() - last_scale) (TouchPhase::Moved, recognizer.scale() - last_scale)
}, }
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
self.ivars().rotation_last_delta.set(0.0); self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed // Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.scale()) (TouchPhase::Cancelled, -recognizer.scale())
}, }
state => panic!("unexpected recognizer state: {state:?}"), state => panic!("unexpected recognizer state: {state:?}"),
}; };
let gesture_event = EventWrapper::Window { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::PinchGesture { device_id: None, delta: delta as f64, phase }, event: WindowEvent::PinchGesture {
device_id: None,
delta: delta as f64,
phase,
},
}; };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
} }
#[unsafe(method(doubleTapGesture:))] #[method(doubleTapGesture:)]
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) { fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended { if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::Window { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::DoubleTapGesture { device_id: None }, event: WindowEvent::DoubleTapGesture {
device_id: None,
},
}; };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
@@ -199,7 +221,7 @@ define_class!(
} }
} }
#[unsafe(method(rotationGesture:))] #[method(rotationGesture:)]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) { fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
@@ -208,24 +230,23 @@ define_class!(
self.ivars().rotation_last_delta.set(0.0); self.ivars().rotation_last_delta.set(0.0);
(TouchPhase::Started, 0.0) (TouchPhase::Started, 0.0)
}, }
UIGestureRecognizerState::Changed => { UIGestureRecognizerState::Changed => {
let last_rotation = let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation());
self.ivars().rotation_last_delta.replace(recognizer.rotation());
(TouchPhase::Moved, recognizer.rotation() - last_rotation) (TouchPhase::Moved, recognizer.rotation() - last_rotation)
}, }
UIGestureRecognizerState::Ended => { UIGestureRecognizerState::Ended => {
let last_rotation = self.ivars().rotation_last_delta.replace(0.0); let last_rotation = self.ivars().rotation_last_delta.replace(0.0);
(TouchPhase::Ended, recognizer.rotation() - last_rotation) (TouchPhase::Ended, recognizer.rotation() - last_rotation)
}, }
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
self.ivars().rotation_last_delta.set(0.0); self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed // Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.rotation()) (TouchPhase::Cancelled, -recognizer.rotation())
}, }
state => panic!("unexpected recognizer state: {state:?}"), state => panic!("unexpected recognizer state: {state:?}"),
}; };
@@ -243,7 +264,7 @@ define_class!(
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
} }
#[unsafe(method(panGesture:))] #[method(panGesture:)]
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) { fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
@@ -254,7 +275,7 @@ define_class!(
self.ivars().pan_last_delta.set(translation); self.ivars().pan_last_delta.set(translation);
(TouchPhase::Started, 0.0, 0.0) (TouchPhase::Started, 0.0, 0.0)
}, }
UIGestureRecognizerState::Changed => { UIGestureRecognizerState::Changed => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation); let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation);
@@ -262,26 +283,25 @@ define_class!(
let dy = translation.y - last_pan.y; let dy = translation.y - last_pan.y;
(TouchPhase::Moved, dx, dy) (TouchPhase::Moved, dx, dy)
}, }
UIGestureRecognizerState::Ended => { UIGestureRecognizerState::Ended => {
let last_pan: CGPoint = let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
let dx = translation.x - last_pan.x; let dx = translation.x - last_pan.x;
let dy = translation.y - last_pan.y; let dy = translation.y - last_pan.y;
(TouchPhase::Ended, dx, dy) (TouchPhase::Ended, dx, dy)
}, }
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
let last_pan: CGPoint = let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
// Pass -delta so that action is reversed // Pass -delta so that action is reversed
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y) (TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
}, }
state => panic!("unexpected recognizer state: {state:?}"), state => panic!("unexpected recognizer state: {state:?}"),
}; };
let gesture_event = EventWrapper::Window { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::PanGesture { event: WindowEvent::PanGesture {
@@ -295,7 +315,7 @@ define_class!(
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
} }
#[unsafe(method(canBecomeFirstResponder))] #[method(canBecomeFirstResponder)]
fn can_become_first_responder(&self) -> bool { fn can_become_first_responder(&self) -> bool {
true true
} }
@@ -304,30 +324,27 @@ define_class!(
unsafe impl NSObjectProtocol for WinitView {} unsafe impl NSObjectProtocol for WinitView {}
unsafe impl UIGestureRecognizerDelegate for WinitView { unsafe impl UIGestureRecognizerDelegate for WinitView {
#[unsafe(method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:))] #[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
fn should_recognize_simultaneously( fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool {
&self,
_gesture_recognizer: &UIGestureRecognizer,
_other_gesture_recognizer: &UIGestureRecognizer,
) -> bool {
true true
} }
} }
unsafe impl UITextInputTraits for WinitView {} unsafe impl UITextInputTraits for WinitView {
}
unsafe impl UIKeyInput for WinitView { unsafe impl UIKeyInput for WinitView {
#[unsafe(method(hasText))] #[method(hasText)]
fn has_text(&self) -> bool { fn has_text(&self) -> bool {
true true
} }
#[unsafe(method(insertText:))] #[method(insertText:)]
fn insert_text(&self, text: &NSString) { fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text) self.handle_insert_text(text)
} }
#[unsafe(method(deleteBackward))] #[method(deleteBackward)]
fn delete_backward(&self) { fn delete_backward(&self) {
self.handle_delete_backward() self.handle_delete_backward()
} }
@@ -353,7 +370,7 @@ impl WinitView {
primary_finger: Cell::new(None), primary_finger: Cell::new(None),
fingers: Cell::new(0), fingers: Cell::new(0),
}); });
let this: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] }; let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
this.setMultipleTouchEnabled(true); this.setMultipleTouchEnabled(true);
@@ -365,8 +382,8 @@ impl WinitView {
} }
fn window(&self) -> Option<Retained<WinitUIWindow>> { fn window(&self) -> Option<Retained<WinitUIWindow>> {
// `WinitView`s should always be installed in a `WinitUIWindow` // SAFETY: `WinitView`s are always installed in a `WinitUIWindow`
(**self).window().map(|window| window.downcast().unwrap()) (**self).window().map(|window| unsafe { Retained::cast(window) })
} }
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) { pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
@@ -461,12 +478,13 @@ impl WinitView {
fn handle_touches(&self, touches: &NSSet<UITouch>) { fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap(); let window = self.window().unwrap();
let mut touch_events = Vec::new(); let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches { for touch in touches {
let logical_location = touch.locationInView(None); let logical_location = touch.locationInView(None);
let touch_type = touch.r#type(); let touch_type = touch.r#type();
let force = if let UITouchType::Pencil = touch_type { let force = if let UITouchType::Pencil = touch_type {
None None
} else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) { } else if os_supports_force {
let trait_collection = self.traitCollection(); let trait_collection = self.traitCollection();
let touch_capability = trait_collection.forceTouchCapability(); let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support. // Both the OS _and_ the device need to be checked for force touch support.
@@ -483,7 +501,7 @@ impl WinitView {
} else { } else {
None None
}; };
let touch_id = Retained::as_ptr(&touch) as usize; let touch_id = touch as *const UITouch as usize;
let phase = touch.phase(); let phase = touch.phase();
let position = { let position = {
let scale_factor = self.contentScaleFactor(); let scale_factor = self.contentScaleFactor();
@@ -643,12 +661,7 @@ impl WinitView {
repeat: false, repeat: false,
logical_key: Key::Character(text.clone()), logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified), physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
text_with_all_modifiers: if state == ElementState::Pressed { platform_specific: KeyEventExtra {},
Some(text.clone())
} else {
None
},
key_without_modifiers: Key::Character(text.clone()),
}, },
is_synthetic: false, is_synthetic: false,
}, },
@@ -671,11 +684,10 @@ impl WinitView {
state, state,
logical_key: Key::Named(NamedKey::Backspace), logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace), physical_key: PhysicalKey::Code(KeyCode::Backspace),
platform_specific: KeyEventExtra {},
repeat: false, repeat: false,
location: KeyLocation::Standard, location: KeyLocation::Standard,
text: None, text: None,
text_with_all_modifiers: None,
key_without_modifiers: Key::Named(NamedKey::Backspace),
}, },
is_synthetic: false, is_synthetic: false,
}, },

View File

@@ -1,13 +1,14 @@
use std::cell::Cell; use std::cell::Cell;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::{available, define_class, msg_send, DefinedClass, MainThreadMarker}; use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_foundation::NSObject; use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_ui_kit::{ use objc2_ui_kit::{
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle, UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
UIUserInterfaceIdiom, UIView, UIViewController, UIUserInterfaceIdiom, UIView, UIViewController,
}; };
use super::app_state::{self};
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::window::WindowAttributes; use crate::window::WindowAttributes;
@@ -19,42 +20,51 @@ pub struct ViewControllerState {
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>, preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
} }
define_class!( declare_class!(
#[unsafe(super(UIViewController, UIResponder, NSObject))]
#[name = "WinitUIViewController"]
#[ivars = ViewControllerState]
pub(crate) struct WinitViewController; pub(crate) struct WinitViewController;
/// This documentation attribute makes rustfmt work for some reason? unsafe impl ClassType for WinitViewController {
impl WinitViewController { #[inherits(UIResponder, NSObject)]
#[unsafe(method(shouldAutorotate))] type Super = UIViewController;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIViewController";
}
impl DeclaredClass for WinitViewController {
type Ivars = ViewControllerState;
}
unsafe impl WinitViewController {
#[method(shouldAutorotate)]
fn should_autorotate(&self) -> bool { fn should_autorotate(&self) -> bool {
true true
} }
#[unsafe(method(prefersStatusBarHidden))] #[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool { fn prefers_status_bar_hidden(&self) -> bool {
self.ivars().prefers_status_bar_hidden.get() self.ivars().prefers_status_bar_hidden.get()
} }
#[unsafe(method(preferredStatusBarStyle))] #[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle { fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.ivars().preferred_status_bar_style.get() self.ivars().preferred_status_bar_style.get()
} }
#[unsafe(method(prefersHomeIndicatorAutoHidden))] #[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool { fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.ivars().prefers_home_indicator_auto_hidden.get() self.ivars().prefers_home_indicator_auto_hidden.get()
} }
#[unsafe(method(supportedInterfaceOrientations))] #[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask { fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.ivars().supported_orientations.get() self.ivars().supported_orientations.get()
} }
#[unsafe(method(preferredScreenEdgesDeferringSystemGestures))] #[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.ivars().preferred_screen_edges_deferring_system_gestures.get() self.ivars()
.preferred_screen_edges_deferring_system_gestures
.get()
} }
} }
); );
@@ -77,13 +87,11 @@ impl WinitViewController {
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.ivars().prefers_home_indicator_auto_hidden.set(val); self.ivars().prefers_home_indicator_auto_hidden.set(val);
if available!(ios = 11.0, visionos = 1.0) { let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden(); self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else { } else {
tracing::warn!( os_capabilities.home_indicator_hidden_err_msg("ignoring")
"`setNeedsUpdateOfHomeIndicatorAutoHidden` requires iOS 11.0+ or visionOS. \
Ignoring"
);
} }
} }
@@ -93,13 +101,11 @@ impl WinitViewController {
UIRectEdge(val.bits().into()) UIRectEdge(val.bits().into())
}; };
self.ivars().preferred_screen_edges_deferring_system_gestures.set(val); self.ivars().preferred_screen_edges_deferring_system_gestures.set(val);
if available!(ios = 11.0, visionos = 1.0) { let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures(); self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else { } else {
tracing::warn!( os_capabilities.defer_system_gestures_err_msg("ignoring")
"`setNeedsUpdateOfScreenEdgesDeferringSystemGestures` requires iOS 11.0+ or \
visionOS. Ignoring"
);
} }
} }
@@ -140,7 +146,7 @@ impl WinitViewController {
supported_orientations: Cell::new(UIInterfaceOrientationMask::All), supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()), preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
}); });
let this: Retained<Self> = unsafe { msg_send![super(this), init] }; let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.set_prefers_status_bar_hidden( this.set_prefers_status_bar_hidden(
window_attributes.platform_specific.prefers_status_bar_hidden, window_attributes.platform_specific.prefers_status_bar_hidden,

View File

@@ -1,13 +1,12 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Arc;
use dispatch2::MainThreadBound;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::{available, class, define_class, msg_send, MainThreadMarker}; use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize}; use objc2_foundation::{
use objc2_foundation::{NSObject, NSObjectProtocol}; CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObject, NSObjectProtocol,
};
use objc2_ui_kit::{ use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen, UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow, UIScreenOverscanCompensation, UIViewController, UIWindow,
@@ -17,7 +16,7 @@ use tracing::{debug, warn};
use super::app_state::EventWrapper; use super::app_state::EventWrapper;
use super::view::WinitView; use super::view::WinitView;
use super::view_controller::WinitViewController; use super::view_controller::WinitViewController;
use super::{app_state, monitor, ActiveEventLoop, MonitorHandle}; use super::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle};
use crate::cursor::Cursor; use crate::cursor::Cursor;
use crate::dpi::{ use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
@@ -26,38 +25,50 @@ use crate::dpi::{
use crate::error::{NotSupportedError, RequestError}; use crate::error::{NotSupportedError, RequestError};
use crate::event::WindowEvent; use crate::event::WindowEvent;
use crate::icon::Icon; use crate::icon::Icon;
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::window::{ use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowAttributes, WindowButtons, WindowId, WindowLevel,
}; };
define_class!( declare_class!(
#[unsafe(super(UIWindow, UIResponder, NSObject))]
#[name = "WinitUIWindow"]
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow; pub(crate) struct WinitUIWindow;
/// This documentation attribute makes rustfmt work for some reason? unsafe impl ClassType for WinitUIWindow {
impl WinitUIWindow { #[inherits(UIResponder, NSObject)]
#[unsafe(method(becomeKeyWindow))] type Super = UIWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIWindow";
}
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)]
fn become_key_window(&self) { fn become_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::Window { app_state::handle_nonuser_event(
window_id: self.id(), mtm,
event: WindowEvent::Focused(true), EventWrapper::Window {
}); window_id: self.id(),
event: WindowEvent::Focused(true),
},
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
} }
#[unsafe(method(resignKeyWindow))] #[method(resignKeyWindow)]
fn resign_key_window(&self) { fn resign_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::Window { app_state::handle_nonuser_event(
window_id: self.id(), mtm,
event: WindowEvent::Focused(false), EventWrapper::Window {
}); window_id: self.id(),
event: WindowEvent::Focused(false),
},
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
} }
} }
@@ -75,23 +86,18 @@ impl WinitUIWindow {
// into very confusing issues with the window not being properly activated. // into very confusing issues with the window not being properly activated.
// //
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events. // Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let this: Retained<Self> = unsafe { msg_send![mtm.alloc(), initWithFrame: frame] }; let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller)); this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.clone() { match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(monitor, ref video_mode)) => { Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap(); let monitor = video_mode.monitor();
let screen = monitor.ui_screen(mtm); let screen = monitor.ui_screen(mtm);
if let Some(video_mode) = screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
{
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
}
this.setScreen(screen); this.setScreen(screen);
}, },
Some(Fullscreen::Borderless(Some(ref monitor))) => { Some(Fullscreen::Borderless(Some(ref monitor))) => {
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap();
let screen = monitor.ui_screen(mtm); let screen = monitor.ui_screen(mtm);
this.setScreen(screen); this.setScreen(screen);
}, },
@@ -199,7 +205,8 @@ impl Inner {
} }
pub fn safe_area(&self) -> PhysicalInsets<u32> { pub fn safe_area(&self) -> PhysicalInsets<u32> {
let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) { // Only available on iOS 11.0
let insets = if app_state::os_capabilities().safe_area {
self.view.safeAreaInsets() self.view.safeAreaInsets()
} else { } else {
// Assume the status bar frame is the only thing that obscures the view // Assume the status bar frame is the only thing that obscures the view
@@ -305,19 +312,12 @@ impl Inner {
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) { pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
let uiscreen = match &monitor { let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(monitor, video_mode)) => { Some(Fullscreen::Exclusive(video_mode)) => {
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap(); let uiscreen = video_mode.monitor.ui_screen(mtm);
let uiscreen = monitor.ui_screen(mtm); uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
if let Some(video_mode) =
monitor.video_modes_handles().find(|mode| &mode.mode == video_mode)
{
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
}
uiscreen.clone() uiscreen.clone()
}, },
Some(Fullscreen::Borderless(Some(monitor))) => { Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
monitor.cast_ref::<MonitorHandle>().unwrap().ui_screen(mtm).clone()
},
Some(Fullscreen::Borderless(None)) => { Some(Fullscreen::Borderless(None)) => {
self.current_monitor_inner().ui_screen(mtm).clone() self.current_monitor_inner().ui_screen(mtm).clone()
}, },
@@ -355,7 +355,7 @@ impl Inner {
&& screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height && screen_space_bounds.size.height == screen_bounds.size.height
{ {
Some(Fullscreen::Borderless(Some(CoreMonitorHandle(Arc::new(monitor))))) Some(Fullscreen::Borderless(Some(monitor)))
} else { } else {
None None
} }
@@ -465,7 +465,6 @@ impl Inner {
} }
} }
#[derive(Debug)]
pub struct Window { pub struct Window {
inner: MainThreadBound<Inner>, inner: MainThreadBound<Inner>,
} }
@@ -488,13 +487,10 @@ impl Window {
#[allow(deprecated)] #[allow(deprecated)]
let main_screen = UIScreen::mainScreen(mtm); let main_screen = UIScreen::mainScreen(mtm);
let fullscreen = window_attributes.fullscreen.clone(); let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen { let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref monitor, _)) Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
| Some(Fullscreen::Borderless(Some(ref monitor))) => { Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap();
monitor.ui_screen(mtm)
},
Some(Fullscreen::Borderless(None)) | None => &main_screen, Some(Fullscreen::Borderless(None)) | None => &main_screen,
}; };
@@ -679,12 +675,12 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.is_maximized()) self.maybe_wait_on_main(|delegate| delegate.is_maximized())
} }
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) { fn set_fullscreen(&self, fullscreen: Option<crate::window::Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen)) self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
} }
fn fullscreen(&self) -> Option<Fullscreen> { fn fullscreen(&self) -> Option<crate::window::Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen()) self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
} }
fn set_decorations(&self, decorations: bool) { fn set_decorations(&self, decorations: bool) {
@@ -780,24 +776,21 @@ impl CoreWindow for Window {
fn current_monitor(&self) -> Option<CoreMonitorHandle> { fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| { self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor))) delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
}) })
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| { self.maybe_wait_on_main(|delegate| {
Box::new( Box::new(
delegate delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
.available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
) )
}) })
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| { self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor))) delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
}) })
} }

View File

@@ -166,8 +166,8 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
122 => KeyCode::Lang1, 122 => KeyCode::Lang1,
123 => KeyCode::Lang2, 123 => KeyCode::Lang2,
124 => KeyCode::IntlYen, 124 => KeyCode::IntlYen,
125 => KeyCode::MetaLeft, 125 => KeyCode::SuperLeft,
126 => KeyCode::MetaRight, 126 => KeyCode::SuperRight,
127 => KeyCode::ContextMenu, 127 => KeyCode::ContextMenu,
128 => KeyCode::BrowserStop, 128 => KeyCode::BrowserStop,
129 => KeyCode::Again, 129 => KeyCode::Again,
@@ -419,8 +419,8 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::Lang1 => Some(122), KeyCode::Lang1 => Some(122),
KeyCode::Lang2 => Some(123), KeyCode::Lang2 => Some(123),
KeyCode::IntlYen => Some(124), KeyCode::IntlYen => Some(124),
KeyCode::MetaLeft => Some(125), KeyCode::SuperLeft => Some(125),
KeyCode::MetaRight => Some(126), KeyCode::SuperRight => Some(126),
KeyCode::ContextMenu => Some(127), KeyCode::ContextMenu => Some(127),
KeyCode::BrowserStop => Some(128), KeyCode::BrowserStop => Some(128),
KeyCode::Again => Some(129), KeyCode::Again => Some(129),
@@ -622,20 +622,16 @@ pub fn keysym_to_key(keysym: u32) -> Key {
keysyms::Control_R => NamedKey::Control, keysyms::Control_R => NamedKey::Control,
keysyms::Caps_Lock => NamedKey::CapsLock, keysyms::Caps_Lock => NamedKey::CapsLock,
// keysyms::Shift_Lock => NamedKey::ShiftLock, // keysyms::Shift_Lock => NamedKey::ShiftLock,
// keysyms::Meta_L => NamedKey::Meta,
// keysyms::Meta_R => NamedKey::Meta,
keysyms::Alt_L => NamedKey::Alt, keysyms::Alt_L => NamedKey::Alt,
keysyms::Alt_R => NamedKey::Alt, keysyms::Alt_R => NamedKey::Alt,
#[allow(deprecated)] keysyms::Super_L => NamedKey::Super,
keysyms::Super_R => NamedKey::Super,
keysyms::Hyper_L => NamedKey::Hyper, keysyms::Hyper_L => NamedKey::Hyper,
#[allow(deprecated)]
keysyms::Hyper_R => NamedKey::Hyper, keysyms::Hyper_R => NamedKey::Hyper,
// Browsers map X11's Super keys to Meta, so we do that as well.
keysyms::Super_L => NamedKey::Meta,
keysyms::Super_R => NamedKey::Meta,
// The actual Meta keys do not seem to be used by browsers, so we don't do that either.
// keysyms::Meta_L => NamedKey::Super,
// keysyms::Meta_R => NamedKey::Super,
// XKB function and modifier keys // XKB function and modifier keys
// keysyms::ISO_Lock => NamedKey::IsoLock, // keysyms::ISO_Lock => NamedKey::IsoLock,
// keysyms::ISO_Level2_Latch => NamedKey::IsoLevel2Latch, // keysyms::ISO_Level2_Latch => NamedKey::IsoLevel2Latch,
@@ -728,7 +724,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
keysyms::_3270_PrintScreen => NamedKey::PrintScreen, keysyms::_3270_PrintScreen => NamedKey::PrintScreen,
keysyms::_3270_Enter => NamedKey::Enter, keysyms::_3270_Enter => NamedKey::Enter,
keysyms::space => return Key::Character(" ".into()), keysyms::space => NamedKey::Space,
// exclam..Sinh_kunddaliya // exclam..Sinh_kunddaliya
// XFree86 // XFree86

View File

@@ -16,6 +16,7 @@ use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle
use crate::event::{ElementState, KeyEvent}; use crate::event::{ElementState, KeyEvent};
use crate::keyboard::{Key, KeyLocation}; use crate::keyboard::{Key, KeyLocation};
use crate::platform_impl::KeyEventExtra;
use crate::utils::Lazy; use crate::utils::Lazy;
mod compose; mod compose;
@@ -197,16 +198,9 @@ impl KeyContext<'_> {
let (key_without_modifiers, _) = event.key_without_modifiers(); let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers(); let text_with_all_modifiers = event.text_with_all_modifiers();
KeyEvent { let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers };
physical_key,
logical_key, KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific }
text,
location,
state,
repeat,
text_with_all_modifiers,
key_without_modifiers,
}
} }
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> { fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {

View File

@@ -183,7 +183,7 @@ impl From<ModifiersState> for crate::keyboard::ModifiersState {
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift); to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl); to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt); to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(crate::keyboard::ModifiersState::META, mods.logo); to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
to_mods to_mods
} }
} }

View File

@@ -4,18 +4,31 @@
compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
use std::env; use std::env;
use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::time::Duration; use std::time::Duration;
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex};
use smol_str::SmolStr;
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey}; pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
#[cfg(x11_platform)]
use self::x11::{XConnection, XError, XNotSupported};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
#[cfg(x11_platform)] #[cfg(x11_platform)]
use crate::dpi::Size; use crate::dpi::Size;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::{EventLoopError, NotSupportedError}; use crate::error::{EventLoopError, NotSupportedError};
use crate::event_loop::ActiveEventLoop; use crate::event_loop::ActiveEventLoop;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus; use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)] #[cfg(x11_platform)]
use crate::platform::x11::WindowType as XWindowType; use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
#[cfg(x11_platform)]
use crate::utils::Lazy;
use crate::window::ActivationToken; use crate::window::ActivationToken;
pub(crate) mod common; pub(crate) mod common;
@@ -90,6 +103,18 @@ impl Default for PlatformSpecificWindowAttributes {
} }
} }
#[cfg(x11_platform)]
pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum MonitorHandle {
#[cfg(x11_platform)]
X(x11::MonitorHandle),
#[cfg(wayland_platform)]
Wayland(wayland::MonitorHandle),
}
/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())` /// `x11_or_wayland!(match expr; Enum(foo) => foo.something())`
/// expands to the equivalent of /// expands to the equivalent of
/// ```ignore /// ```ignore
@@ -118,7 +143,133 @@ macro_rules! x11_or_wayland {
}; };
} }
#[derive(Debug)] impl MonitorHandle {
#[inline]
pub fn name(&self) -> Option<String> {
x11_or_wayland!(match self; MonitorHandle(m) => m.name())
}
#[inline]
pub fn native_identifier(&self) -> u32 {
x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier())
}
#[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
x11_or_wayland!(match self; MonitorHandle(m) => m.position())
}
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _)
}
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
}
#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoModeHandle {
#[cfg(x11_platform)]
X(x11::VideoModeHandle),
#[cfg(wayland_platform)]
Wayland(wayland::VideoModeHandle),
}
impl VideoModeHandle {
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.size())
}
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth())
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz())
}
#[inline]
pub fn monitor(&self) -> MonitorHandle {
x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) enum PlatformCustomCursor {
#[cfg(wayland_platform)]
Wayland(wayland::CustomCursor),
#[cfg(x11_platform)]
X(x11::CustomCursor),
}
/// Hooks for X11 errors.
#[cfg(x11_platform)]
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
#[cfg(x11_platform)]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
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<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
unsafe {
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
)
};
let description =
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
let error = unsafe {
XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
}
};
// Don't log error.
if !error_handled {
tracing::error!("X11 error: {:#?}", error);
// XXX only update the error, if it wasn't handled by any of the hooks.
*xconn.latest_error.lock().unwrap() = Some(error);
}
}
// Fun fact: this return value is completely ignored.
0
}
pub enum EventLoop { pub enum EventLoop {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop>), Wayland(Box<wayland::EventLoop>),
@@ -178,9 +329,9 @@ impl EventLoop {
// Create the display based on the backend. // Create the display based on the backend.
match backend { match backend {
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread(), Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
#[cfg(x11_platform)] #[cfg(x11_platform)]
Backend::X => EventLoop::new_x11_any_thread(), Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
} }
} }
@@ -191,7 +342,12 @@ impl EventLoop {
#[cfg(x11_platform)] #[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> { fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
x11::EventLoop::new().map(EventLoop::X) let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(os_error!(err.clone()).into()),
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
} }
#[inline] #[inline]

View File

@@ -3,33 +3,27 @@
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::io::Result as IOResult; use std::io::Result as IOResult;
use std::mem; use std::mem;
use std::os::fd::OwnedFd;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, Condvar, Mutex}; use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use calloop::ping::Ping;
use rustix::event::{PollFd, PollFlags};
use rustix::pipe::{self, PipeFlags};
use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle}; use sctk::reexports::client::{globals, Connection, QueueHandle};
use tracing::warn;
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize; use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, NotSupportedError, OsError, RequestError}; use crate::error::{EventLoopError, OsError, RequestError};
use crate::event::{DeviceEvent, StartCause, SurfaceSizeWriter, WindowEvent}; use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::{ use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
OwnedDisplayHandle as CoreOwnedDisplayHandle, OwnedDisplayHandle as CoreOwnedDisplayHandle,
}; };
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::pump_events::PumpStatus; use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout; use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::wayland::types::cursor::WaylandCustomCursor; use crate::platform_impl::PlatformCustomCursor;
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme}; use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
mod proxy; mod proxy;
pub mod sink; pub mod sink;
@@ -37,7 +31,6 @@ pub mod sink;
use proxy::EventLoopProxy; use proxy::EventLoopProxy;
use sink::EventSink; use sink::EventSink;
use super::output::MonitorHandle;
use super::state::{WindowCompositorUpdate, WinitState}; use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState; use super::window::state::FrameCallbackState;
use super::{logical_to_physical_rounded, WindowId}; use super::{logical_to_physical_rounded, WindowId};
@@ -45,14 +38,7 @@ pub use crate::event_loop::EventLoopProxy as CoreEventLoopProxy;
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>; type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
#[derive(Debug)]
pub(crate) enum Event {
WindowEvent { window_id: WindowId, event: WindowEvent },
DeviceEvent { event: DeviceEvent },
}
/// The Wayland event loop. /// The Wayland event loop.
#[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop /// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
loop_running: bool, loop_running: bool,
@@ -74,8 +60,6 @@ pub struct EventLoop {
// XXX drop after everything else, just to be safe. // XXX drop after everything else, just to be safe.
/// Calloop's event loop. /// Calloop's event loop.
event_loop: calloop::EventLoop<'static, WinitState>, event_loop: calloop::EventLoop<'static, WinitState>,
pump_event_notifier: Option<PumpEventNotifier>,
} }
impl EventLoop { impl EventLoop {
@@ -157,7 +141,6 @@ impl EventLoop {
wayland_dispatcher, wayland_dispatcher,
event_loop, event_loop,
active_event_loop, active_event_loop,
pump_event_notifier: None,
}; };
Ok(event_loop) Ok(event_loop)
@@ -212,27 +195,13 @@ impl EventLoop {
if !self.exiting() { if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut app); self.poll_events_with_timeout(timeout, &mut app);
} }
if let Some(code) = self.exit_code() { if let Some(code) = self.exit_code() {
self.loop_running = false; self.loop_running = false;
app.exiting(&self.active_event_loop);
PumpStatus::Exit(code) PumpStatus::Exit(code)
} else { } else {
// NOTE: spawn a wake-up thread, thus if we have code reading the wayland connection
// in parallel to winit, we ensure that the loop itself is marked as having events.
if timeout.is_some() && self.pump_event_notifier.is_none() {
self.pump_event_notifier = Some(PumpEventNotifier::spawn(
self.active_event_loop.handle.connection.clone(),
self.active_event_loop.event_loop_awakener.clone(),
));
}
if let Some(pump_event_notifier) = self.pump_event_notifier.as_ref() {
// Notify that we don't have to wait, since we're out of winit.
*pump_event_notifier.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
pump_event_notifier.control.1.notify_one();
}
PumpStatus::Continue PumpStatus::Continue
} }
} }
@@ -296,10 +265,7 @@ impl EventLoop {
// Reduce spurious wake-ups. // Reduce spurious wake-ups.
let dispatched_events = self.with_state(|state| state.dispatched_events); let dispatched_events = self.with_state(|state| state.dispatched_events);
if matches!(cause, StartCause::WaitCancelled { .. }) if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
&& !dispatched_events
&& timeout.is_none()
{
continue; continue;
} }
@@ -417,9 +383,10 @@ impl EventLoop {
Event::WindowEvent { window_id, event } => { Event::WindowEvent { window_id, event } => {
app.window_event(&self.active_event_loop, window_id, event) app.window_event(&self.active_event_loop, window_id, event)
}, },
Event::DeviceEvent { event } => { Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, None, event) app.device_event(&self.active_event_loop, device_id, event)
}, },
_ => unreachable!("event which is neither device nor window event."),
} }
} }
@@ -432,9 +399,10 @@ impl EventLoop {
Event::WindowEvent { window_id, event } => { Event::WindowEvent { window_id, event } => {
app.window_event(&self.active_event_loop, window_id, event) app.window_event(&self.active_event_loop, window_id, event)
}, },
Event::DeviceEvent { event } => { Event::DeviceEvent { device_id, event } => {
app.device_event(&self.active_event_loop, None, event) app.device_event(&self.active_event_loop, device_id, event)
}, },
_ => unreachable!("event which is neither device nor window event."),
} }
} }
@@ -574,13 +542,12 @@ impl AsRawFd for EventLoop {
} }
} }
#[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
/// Event loop proxy /// Event loop proxy
event_loop_proxy: CoreEventLoopProxy, event_loop_proxy: CoreEventLoopProxy,
/// The event loop wakeup source. /// The event loop wakeup source.
pub event_loop_awakener: Ping, pub event_loop_awakener: calloop::ping::Ping,
/// The main queue used by the event loop. /// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>, pub queue_handle: QueueHandle<WinitState>,
@@ -629,15 +596,10 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor( fn create_custom_cursor(
&self, &self,
cursor: CustomCursorSource, cursor: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> { ) -> Result<RootCustomCursor, RequestError> {
let cursor_image = match cursor { Ok(RootCustomCursor {
CustomCursorSource::Image(cursor_image) => cursor_image, inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))),
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => { })
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
Ok(CoreCustomCursor(Arc::new(WaylandCustomCursor(cursor_image))))
} }
#[inline] #[inline]
@@ -653,18 +615,19 @@ impl RootActiveEventLoop for ActiveEventLoop {
Ok(Box::new(window)) Ok(Box::new(window))
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
Box::new( Box::new(
self.state self.state
.borrow() .borrow()
.output_state .output_state
.outputs() .outputs()
.map(MonitorHandle::new) .map(crate::platform_impl::wayland::output::MonitorHandle::new)
.map(|inner| CoreMonitorHandle(Arc::new(inner))), .map(crate::platform_impl::MonitorHandle::Wayland)
.map(|inner| crate::monitor::MonitorHandle { inner }),
) )
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
// There's no primary monitor on Wayland. // There's no primary monitor on Wayland.
None None
} }
@@ -698,7 +661,6 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
} }
} }
#[derive(Debug)]
pub struct OwnedDisplayHandle { pub struct OwnedDisplayHandle {
pub(crate) connection: Connection, pub(crate) connection: Connection,
} }
@@ -721,84 +683,3 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw.into()) }) Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw.into()) })
} }
} }
#[derive(Debug)]
struct PumpEventNotifier {
/// Whether we're in winit or not.
control: Arc<(Mutex<PumpEventNotifierAction>, Condvar)>,
/// Waker handle for the working thread.
worker_waker: Option<OwnedFd>,
/// Thread handle.
handle: Option<JoinHandle<()>>,
}
impl Drop for PumpEventNotifier {
fn drop(&mut self) {
// Wake-up the thread.
if let Some(worker_waker) = self.worker_waker.as_ref() {
let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]);
}
*self.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
self.control.1.notify_one();
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
impl PumpEventNotifier {
fn spawn(connection: Connection, awakener: Ping) -> Self {
// Start from the waiting state.
let control = Arc::new((Mutex::new(PumpEventNotifierAction::Pause), Condvar::new()));
let control_thread = Arc::clone(&control);
let (read, write) = match pipe::pipe_with(PipeFlags::CLOEXEC | PipeFlags::NONBLOCK) {
Ok((read, write)) => (read, write),
Err(_) => return Self { control, handle: None, worker_waker: None },
};
let handle =
std::thread::Builder::new().name(String::from("pump_events mon")).spawn(move || {
let (lock, cvar) = &*control_thread;
'outer: loop {
let mut wait = lock.lock().unwrap();
while *wait == PumpEventNotifierAction::Pause {
wait = cvar.wait(wait).unwrap();
}
// Wake-up the main loop and put this one back to sleep.
*wait = PumpEventNotifierAction::Pause;
drop(wait);
while let Some(read_guard) = connection.prepare_read() {
let _ = connection.flush();
let poll_fd = PollFd::from_borrowed_fd(connection.as_fd(), PollFlags::IN);
let pipe_poll_fd = PollFd::from_borrowed_fd(read.as_fd(), PollFlags::IN);
// Read from the `fd` before going back to poll.
if Ok(1) == rustix::io::read(read.as_fd(), &mut [0u8; 1]) {
break 'outer;
}
let _ = rustix::event::poll(&mut [poll_fd, pipe_poll_fd], -1);
// Non-blocking read the connection.
let _ = read_guard.read_without_dispatch();
}
awakener.ping();
}
});
if let Some(err) = handle.as_ref().err() {
warn!("failed to spawn pump_events wake-up thread: {err}");
}
PumpEventNotifier { control, handle: handle.ok(), worker_waker: Some(write) }
}
}
#[derive(Debug, PartialEq, Eq)]
enum PumpEventNotifierAction {
/// Monitor the wayland queue.
Monitor,
/// Pause monitoring.
Pause,
}

View File

@@ -7,7 +7,6 @@ use sctk::reexports::calloop::ping::Ping;
use crate::event_loop::{EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider}; use crate::event_loop::{EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider};
/// A handle that can be sent across the threads and used to wake up the `EventLoop`. /// A handle that can be sent across the threads and used to wake up the `EventLoop`.
#[derive(Debug)]
pub struct EventLoopProxy { pub struct EventLoopProxy {
ping: Ping, ping: Ping,
} }

View File

@@ -2,13 +2,12 @@
use std::vec::Drain; use std::vec::Drain;
use super::Event; use crate::event::{DeviceEvent, Event, WindowEvent};
use crate::event::{DeviceEvent, WindowEvent};
use crate::window::WindowId; use crate::window::WindowId;
/// An event loop's sink to deliver events from the Wayland event callbacks /// An event loop's sink to deliver events from the Wayland event callbacks
/// to the winit's user. /// to the winit's user.
#[derive(Default, Debug)] #[derive(Default)]
pub struct EventSink { pub struct EventSink {
pub(crate) window_events: Vec<Event>, pub(crate) window_events: Vec<Event>,
} }
@@ -27,7 +26,7 @@ impl EventSink {
/// Add new device event to a queue. /// Add new device event to a queue.
#[inline] #[inline]
pub fn push_device_event(&mut self, event: DeviceEvent) { pub fn push_device_event(&mut self, event: DeviceEvent) {
self.window_events.push(Event::DeviceEvent { event }); self.window_events.push(Event::DeviceEvent { event, device_id: None });
} }
/// Add new window event to a queue. /// Add new window event to a queue.

View File

@@ -1,8 +1,12 @@
//! Winit's Wayland backend. //! Winit's Wayland backend.
pub use event_loop::{ActiveEventLoop, EventLoop};
pub use output::{MonitorHandle, VideoModeHandle};
use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy; use sctk::reexports::client::Proxy;
pub use window::Window;
pub(super) use crate::cursor::OnlyCursorImage as CustomCursor;
use crate::dpi::{LogicalSize, PhysicalSize}; use crate::dpi::{LogicalSize, PhysicalSize};
use crate::window::WindowId; use crate::window::WindowId;
@@ -13,9 +17,6 @@ mod state;
mod types; mod types;
mod window; mod window;
pub use event_loop::{ActiveEventLoop, EventLoop};
pub use window::Window;
/// Get the WindowId out of the surface. /// Get the WindowId out of the surface.
#[inline] #[inline]
fn make_wid(surface: &WlSurface) -> WindowId { fn make_wid(surface: &WlSurface) -> WindowId {

View File

@@ -1,12 +1,11 @@
use std::borrow::Cow; use std::num::{NonZeroU16, NonZeroU32};
use std::num::NonZeroU32;
use sctk::output::{Mode, OutputData}; use sctk::output::{Mode, OutputData};
use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Proxy; use sctk::reexports::client::Proxy;
use crate::dpi::{LogicalPosition, PhysicalPosition}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandleProvider as CoreMonitorHandle, VideoMode}; use crate::platform_impl::platform::VideoModeHandle as PlatformVideoModeHandle;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MonitorHandle { pub struct MonitorHandle {
@@ -18,24 +17,21 @@ impl MonitorHandle {
pub(crate) fn new(proxy: WlOutput) -> Self { pub(crate) fn new(proxy: WlOutput) -> Self {
Self { proxy } Self { proxy }
} }
}
impl CoreMonitorHandle for MonitorHandle { #[inline]
fn id(&self) -> u128 { pub fn name(&self) -> Option<String> {
self.native_id() as _
}
fn native_id(&self) -> u64 {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| info.id as u64) output_data.with_output_info(|info| info.name.clone())
} }
fn name(&self) -> Option<Cow<'_, str>> { #[inline]
pub fn native_identifier(&self) -> u32 {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| info.name.clone().map(Cow::Owned)) output_data.with_output_info(|info| info.id)
} }
fn position(&self) -> Option<PhysicalPosition<i32>> { #[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
Some(output_data.with_output_info(|info| { Some(output_data.with_output_info(|info| {
info.logical_position.map_or_else( info.logical_position.map_or_else(
@@ -51,41 +47,95 @@ impl CoreMonitorHandle for MonitorHandle {
})) }))
} }
fn scale_factor(&self) -> f64 { #[inline]
pub fn scale_factor(&self) -> i32 {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.scale_factor() as f64 output_data.scale_factor()
} }
fn current_video_mode(&self) -> Option<crate::monitor::VideoMode> { #[inline]
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| { output_data.with_output_info(|info| {
let mode = info.modes.iter().find(|mode| mode.current).cloned(); let mode = info.modes.iter().find(|mode| mode.current).cloned();
mode.map(wayland_mode_to_core_mode) mode.map(|mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(self.clone(), mode))
})
}) })
} }
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> { #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
let output_data = self.proxy.data::<OutputData>().unwrap(); let output_data = self.proxy.data::<OutputData>().unwrap();
let modes = output_data.with_output_info(|info| info.modes.clone()); let modes = output_data.with_output_info(|info| info.modes.clone());
Box::new(modes.into_iter().map(wayland_mode_to_core_mode)) let monitor = self.clone();
modes.into_iter().map(move |mode| {
PlatformVideoModeHandle::Wayland(VideoModeHandle::new(monitor.clone(), mode))
})
} }
} }
impl PartialEq for MonitorHandle { impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.native_id() == other.native_id() self.native_identifier() == other.native_identifier()
} }
} }
impl Eq for MonitorHandle {} impl Eq for MonitorHandle {}
/// Convert the wayland's [`Mode`] to winit's [`VideoMode`]. impl PartialOrd for MonitorHandle {
fn wayland_mode_to_core_mode(mode: Mode) -> VideoMode { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
VideoMode { Some(self.cmp(other))
size: (mode.dimensions.0, mode.dimensions.1).into(), }
bit_depth: None, }
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.native_identifier().cmp(&other.native_identifier())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.native_identifier().hash(state);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) size: PhysicalSize<u32>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) monitor: MonitorHandle,
}
impl VideoModeHandle {
fn new(monitor: MonitorHandle, mode: Mode) -> Self {
VideoModeHandle {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: NonZeroU32::new(mode.refresh_rate as u32),
monitor: monitor.clone(),
}
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
None
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
} }
} }

View File

@@ -96,12 +96,8 @@ impl SeatHandler for WinitState {
}, },
SeatCapability::Pointer if seat_state.pointer.is_none() => { SeatCapability::Pointer if seat_state.pointer.is_none() => {
let surface = self.compositor_state.create_surface(queue_handle); let surface = self.compositor_state.create_surface(queue_handle);
let viewport = self
.viewporter_state
.as_ref()
.map(|state| state.get_viewport(&surface, queue_handle));
let surface_id = surface.id(); let surface_id = surface.id();
let pointer_data = WinitPointerData::new(seat.clone(), viewport); let pointer_data = WinitPointerData::new(seat.clone());
let themed_pointer = self let themed_pointer = self
.seat_state .seat_state
.get_pointer_with_theme_and_data( .get_pointer_with_theme_and_data(

View File

@@ -18,7 +18,6 @@ use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_ma
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1}; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::csd_frame::FrameClick; use sctk::reexports::csd_frame::FrameClick;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::compositor::SurfaceData; use sctk::compositor::SurfaceData;
use sctk::globals::GlobalData; use sctk::globals::GlobalData;
@@ -28,10 +27,7 @@ use sctk::seat::pointer::{
use sctk::seat::SeatState; use sctk::seat::SeatState;
use crate::dpi::{LogicalPosition, PhysicalPosition}; use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent};
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, WindowId}; use crate::platform_impl::wayland::{self, WindowId};
@@ -247,17 +243,13 @@ pub struct WinitPointerData {
/// The data required by the sctk. /// The data required by the sctk.
sctk_data: PointerData, sctk_data: PointerData,
/// Viewport for fractional cursor.
viewport: Option<WpViewport>,
} }
impl WinitPointerData { impl WinitPointerData {
pub fn new(seat: WlSeat, viewport: Option<WpViewport>) -> Self { pub fn new(seat: WlSeat) -> Self {
Self { Self {
inner: Mutex::new(WinitPointerDataInner::default()), inner: Mutex::new(WinitPointerDataInner::default()),
sctk_data: PointerData::new(seat), sctk_data: PointerData::new(seat),
viewport,
} }
} }
@@ -338,18 +330,6 @@ impl WinitPointerData {
locked_pointer.set_cursor_position_hint(surface_x, surface_y); locked_pointer.set_cursor_position_hint(surface_x, surface_y);
} }
} }
pub fn viewport(&self) -> Option<&WpViewport> {
self.viewport.as_ref()
}
}
impl Drop for WinitPointerData {
fn drop(&mut self) {
if let Some(viewport) = self.viewport.take() {
viewport.destroy();
}
}
} }
impl PointerDataExt for WinitPointerData { impl PointerDataExt for WinitPointerData {
@@ -431,7 +411,6 @@ impl WinitPointerDataExt for WlPointer {
} }
} }
#[derive(Debug)]
pub struct PointerConstraintsState { pub struct PointerConstraintsState {
pointer_constraints: ZwpPointerConstraintsV1, pointer_constraints: ZwpPointerConstraintsV1,
} }

View File

@@ -16,7 +16,6 @@ use crate::event::DeviceEvent;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
/// Wrapper around the relative pointer. /// Wrapper around the relative pointer.
#[derive(Debug)]
pub struct RelativePointerState { pub struct RelativePointerState {
manager: ZwpRelativePointerManagerV1, manager: ZwpRelativePointerManagerV1,
} }

View File

@@ -14,7 +14,6 @@ use crate::platform_impl::wayland;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
use crate::window::ImePurpose; use crate::window::ImePurpose;
#[derive(Debug)]
pub struct TextInputState { pub struct TextInputState {
text_input_manager: ZwpTextInputManagerV3, text_input_manager: ZwpTextInputManagerV3,
} }
@@ -120,15 +119,11 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
None => return, None => return,
}; };
// Clear preedit, unless all we'll be doing next is sending a new preedit. // Clear preedit at the start of `Done`.
if text_input_data.pending_commit.is_some() state.events_sink.push_window_event(
|| text_input_data.pending_preedit.is_none() WindowEvent::Ime(Ime::Preedit(String::new(), None)),
{ window_id,
state.events_sink.push_window_event( );
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
}
// Send `Commit`. // Send `Commit`.
if let Some(text) = text_input_data.pending_commit.take() { if let Some(text) = text_input_data.pending_commit.take() {

View File

@@ -36,7 +36,6 @@ use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
use crate::platform_impl::wayland::WindowId; use crate::platform_impl::wayland::WindowId;
/// Winit's Wayland state. /// Winit's Wayland state.
#[derive(Debug)]
pub struct WinitState { pub struct WinitState {
/// The WlRegistry. /// The WlRegistry.
pub registry_state: RegistryState, pub registry_state: RegistryState,

View File

@@ -2,16 +2,7 @@ use cursor_icon::CursorIcon;
use sctk::reexports::client::protocol::wl_shm::Format; use sctk::reexports::client::protocol::wl_shm::Format;
use sctk::shm::slot::{Buffer, SlotPool}; use sctk::shm::slot::{Buffer, SlotPool};
use crate::cursor::{CursorImage, CustomCursorProvider}; use crate::cursor::CursorImage;
// Wrap in our own type to not impl trait on global type.
#[derive(Debug)]
pub struct WaylandCustomCursor(pub(crate) CursorImage);
impl CustomCursorProvider for WaylandCustomCursor {
fn is_animated(&self) -> bool {
false
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum SelectedCursor { pub enum SelectedCursor {
@@ -35,8 +26,7 @@ pub struct CustomCursor {
} }
impl CustomCursor { impl CustomCursor {
pub(crate) fn new(pool: &mut SlotPool, image: &WaylandCustomCursor) -> Self { pub(crate) fn new(pool: &mut SlotPool, image: &CursorImage) -> Self {
let image = &image.0;
let (buffer, canvas) = pool let (buffer, canvas) = pool
.create_buffer( .create_buffer(
image.width as i32, image.width as i32,

View File

@@ -16,7 +16,6 @@ use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
use crate::window::{ActivationToken, WindowId}; use crate::window::{ActivationToken, WindowId};
#[derive(Debug)]
pub struct XdgActivationState { pub struct XdgActivationState {
xdg_activation: XdgActivationV1, xdg_activation: XdgActivationV1,
} }
@@ -79,7 +78,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
state.events_sink.push_window_event( state.events_sink.push_window_event(
crate::event::WindowEvent::ActivationTokenDone { crate::event::WindowEvent::ActivationTokenDone {
serial: *serial, serial: *serial,
token: ActivationToken::from_raw(token), token: ActivationToken::_new(token),
}, },
*window_id, *window_id,
); );

View File

@@ -1,7 +1,5 @@
//! The Wayland window. //! The Wayland window.
use std::ffi::c_void;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -23,11 +21,12 @@ use crate::dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Po
use crate::error::{NotSupportedError, RequestError}; use crate::error::{NotSupportedError, RequestError};
use crate::event::{Ime, WindowEvent}; use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial; use crate::event_loop::AsyncRequestSerial;
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform_impl::wayland::output; use crate::platform_impl::{Fullscreen, MonitorHandle as PlatformMonitorHandle};
use crate::window::{ use crate::window::{
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Cursor, CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, ResizeDirection, Theme,
Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
}; };
pub(crate) mod state; pub(crate) mod state;
@@ -35,7 +34,6 @@ pub(crate) mod state;
pub use state::WindowState; pub use state::WindowState;
/// The Wayland window. /// The Wayland window.
#[derive(Debug)]
pub struct Window { pub struct Window {
/// Reference to the underlying SCTK window. /// Reference to the underlying SCTK window.
window: SctkWindow, window: SctkWindow,
@@ -140,17 +138,19 @@ impl Window {
window_state.set_resizable(attributes.resizable); window_state.set_resizable(attributes.resizable);
// Set startup mode. // Set startup mode.
match attributes.fullscreen { match attributes.fullscreen.map(Into::into) {
Some(Fullscreen::Exclusive(..)) => { Some(Fullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland"); warn!("`Fullscreen::Exclusive` is ignored on Wayland");
}, },
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))] #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
Some(Fullscreen::Borderless(monitor)) => { Some(Fullscreen::Borderless(monitor)) => {
let output = monitor.as_ref().and_then(|monitor| { let output = monitor.and_then(|monitor| match monitor {
monitor.cast_ref::<output::MonitorHandle>().map(|handle| &handle.proxy) PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
#[cfg(x11_platform)]
PlatformMonitorHandle::X(_) => None,
}); });
window.set_fullscreen(output) window.set_fullscreen(output.as_ref())
}, },
_ if attributes.maximized => window.set_maximized(), _ if attributes.maximized => window.set_maximized(),
_ => (), _ => (),
@@ -165,7 +165,7 @@ impl Window {
if let (Some(xdg_activation), Some(token)) = if let (Some(xdg_activation), Some(token)) =
(xdg_activation.as_ref(), attributes.platform_specific.activation_token) (xdg_activation.as_ref(), attributes.platform_specific.activation_token)
{ {
xdg_activation.activate(token.token, &surface); xdg_activation.activate(token._token, &surface);
} }
// XXX Do initial commit. // XXX Do initial commit.
@@ -216,10 +216,6 @@ impl Window {
window_events_sink, window_events_sink,
}) })
} }
pub(crate) fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast())
}
} }
impl Window { impl Window {
@@ -440,24 +436,26 @@ impl CoreWindow for Window {
.unwrap_or_default() .unwrap_or_default()
} }
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) { fn set_fullscreen(&self, fullscreen: Option<CoreFullscreen>) {
match fullscreen { match fullscreen {
Some(Fullscreen::Exclusive(..)) => { Some(CoreFullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland"); warn!("`Fullscreen::Exclusive` is ignored on Wayland");
}, },
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))] #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
Some(Fullscreen::Borderless(monitor)) => { Some(CoreFullscreen::Borderless(monitor)) => {
let output = monitor.as_ref().and_then(|monitor| { let output = monitor.and_then(|monitor| match monitor.inner {
monitor.cast_ref::<output::MonitorHandle>().map(|handle| &handle.proxy) PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
#[cfg(x11_platform)]
PlatformMonitorHandle::X(_) => None,
}); });
self.window.set_fullscreen(output) self.window.set_fullscreen(output.as_ref())
}, },
None => self.window.unset_fullscreen(), None => self.window.unset_fullscreen(),
} }
} }
fn fullscreen(&self) -> Option<Fullscreen> { fn fullscreen(&self) -> Option<CoreFullscreen> {
let is_fullscreen = self let is_fullscreen = self
.window_state .window_state
.lock() .lock()
@@ -469,7 +467,7 @@ impl CoreWindow for Window {
if is_fullscreen { if is_fullscreen {
let current_monitor = self.current_monitor(); let current_monitor = self.current_monitor();
Some(Fullscreen::Borderless(current_monitor)) Some(CoreFullscreen::Borderless(current_monitor))
} else { } else {
None None
} }
@@ -629,7 +627,8 @@ impl CoreWindow for Window {
data.outputs() data.outputs()
.next() .next()
.map(MonitorHandle::new) .map(MonitorHandle::new)
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))) .map(crate::platform_impl::MonitorHandle::Wayland)
.map(|inner| CoreMonitorHandle { inner })
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
@@ -639,7 +638,8 @@ impl CoreWindow for Window {
.unwrap() .unwrap()
.clone() .clone()
.into_iter() .into_iter()
.map(|inner| CoreMonitorHandle(Arc::new(inner))), .map(crate::platform_impl::MonitorHandle::Wayland)
.map(|inner| CoreMonitorHandle { inner }),
) )
} }

View File

@@ -28,8 +28,8 @@ use sctk::subcompositor::SubcompositorState;
use tracing::{info, warn}; use tracing::{info, warn};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as CoreCustomCursor; use crate::cursor::CustomCursor as RootCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size}; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{NotSupportedError, RequestError}; use crate::error::{NotSupportedError, RequestError};
use crate::platform_impl::wayland::event_loop::OwnedDisplayHandle; use crate::platform_impl::wayland::event_loop::OwnedDisplayHandle;
use crate::platform_impl::wayland::logical_to_physical_rounded; use crate::platform_impl::wayland::logical_to_physical_rounded;
@@ -37,10 +37,9 @@ use crate::platform_impl::wayland::seat::{
PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
}; };
use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState}; use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
use crate::platform_impl::wayland::types::cursor::{ use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
CustomCursor, SelectedCursor, WaylandCustomCursor,
};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::PlatformCustomCursor;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, WindowId}; use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, WindowId};
#[cfg(feature = "sctk-adwaita")] #[cfg(feature = "sctk-adwaita")]
@@ -52,7 +51,6 @@ pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1); const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
/// The state of the window which is being updated from the [`WinitState`]. /// The state of the window which is being updated from the [`WinitState`].
#[derive(Debug)]
pub struct WindowState { pub struct WindowState {
/// The connection to Wayland server. /// The connection to Wayland server.
pub handle: Arc<OwnedDisplayHandle>, pub handle: Arc<OwnedDisplayHandle>,
@@ -222,9 +220,9 @@ impl WindowState {
} }
/// Apply closure on the given pointer. /// Apply closure on the given pointer.
fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>( fn apply_on_pointer<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self, &self,
mut callback: F, callback: F,
) { ) {
self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| { self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
let data = pointer.pointer().winit_data(); let data = pointer.pointer().winit_data();
@@ -703,18 +701,19 @@ impl WindowState {
} }
/// Set the custom cursor icon. /// Set the custom cursor icon.
pub(crate) fn set_custom_cursor(&mut self, cursor: CoreCustomCursor) { pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
let cursor = match cursor.cast_ref::<WaylandCustomCursor>() { let cursor = match cursor {
Some(cursor) => cursor, RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
None => { #[cfg(x11_platform)]
tracing::error!("unrecognized cursor passed to Wayland backend"); RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
tracing::error!("passed a X11 cursor to Wayland backend");
return; return;
}, },
}; };
let cursor = { let cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap(); let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, cursor) CustomCursor::new(&mut pool, &cursor)
}; };
if self.cursor_visible { if self.cursor_visible {
@@ -725,26 +724,17 @@ impl WindowState {
} }
fn apply_custom_cursor(&self, cursor: &CustomCursor) { fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_pointer(|pointer, data| { self.apply_on_pointer(|pointer, _| {
let surface = pointer.surface(); let surface = pointer.surface();
let scale = if let Some(viewport) = data.viewport() { let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
let scale = self.scale_factor();
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
viewport.set_destination(size.width, size.height);
scale
} else {
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
surface.set_buffer_scale(scale);
scale as f64
};
surface.set_buffer_scale(scale);
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0); surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
if surface.version() >= 4 { if surface.version() >= 4 {
surface.damage_buffer(0, 0, cursor.w, cursor.h); surface.damage_buffer(0, 0, cursor.w, cursor.h);
} else { } else {
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale); surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
surface.damage(0, 0, size.width, size.height);
} }
surface.commit(); surface.commit();
@@ -754,9 +744,12 @@ impl WindowState {
.and_then(|data| data.pointer_data().latest_enter_serial()) .and_then(|data| data.pointer_data().latest_enter_serial())
.unwrap(); .unwrap();
let hotspot = pointer.pointer().set_cursor(
PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale); serial,
pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y); Some(surface),
cursor.hotspot_x / scale,
cursor.hotspot_y / scale,
);
}); });
} }
@@ -836,51 +829,34 @@ impl WindowState {
}, },
}; };
let mut unset_old = false; // Replace the current mode.
match self.cursor_grab_mode.current_grab_mode { let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
CursorGrabMode::None => unset_old = true,
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.apply_on_pointer(|_, data| { CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
data.unconfine_pointer(); data.unconfine_pointer();
unset_old = true;
}), }),
CursorGrabMode::Locked => { CursorGrabMode::Locked => {
self.apply_on_pointer(|_, data| { self.apply_on_pointer(|_, data| data.unlock_pointer());
data.unlock_pointer();
unset_old = true;
});
}, },
} }
// In case we haven't unset the old mode, it means that we don't have a cursor above
// the window, thus just wait for it to re-appear.
if !unset_old {
return Ok(());
}
let mut set_mode = false;
let surface = self.window.wl_surface(); let surface = self.window.wl_surface();
match mode { match mode {
CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| { CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer(); let pointer = pointer.pointer();
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle); data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
set_mode = true;
}), }),
CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| { CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer(); let pointer = pointer.pointer();
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle); data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
set_mode = true;
}), }),
CursorGrabMode::None => { CursorGrabMode::None => {
// Current lock/confine was already removed. // Current lock/confine was already removed.
set_mode = true;
}, },
} }
// Replace the current grab mode after we've ensure that it got updated.
if set_mode {
self.cursor_grab_mode.current_grab_mode = mode;
}
Ok(()) Ok(())
} }
@@ -1121,7 +1097,7 @@ impl Drop for WindowState {
} }
/// The state of the cursor grabs. /// The state of the cursor grabs.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
struct GrabState { struct GrabState {
/// The grab mode requested by the user. /// The grab mode requested by the user.
user_grab_mode: CursorGrabMode, user_grab_mode: CursorGrabMode,

View File

@@ -4,7 +4,6 @@ use std::path::{Path, PathBuf};
use std::str::Utf8Error; use std::str::Utf8Error;
use std::sync::Arc; use std::sync::Arc;
use dpi::PhysicalPosition;
use percent_encoding::percent_decode; use percent_encoding::percent_decode;
use x11rb::protocol::xproto::{self, ConnectionExt}; use x11rb::protocol::xproto::{self, ConnectionExt};
@@ -39,7 +38,6 @@ impl From<io::Error> for DndDataParseError {
} }
} }
#[derive(Debug)]
pub struct Dnd { pub struct Dnd {
xconn: Arc<XConnection>, xconn: Arc<XConnection>,
// Populated by XdndEnter event handler // Populated by XdndEnter event handler
@@ -47,25 +45,13 @@ pub struct Dnd {
pub type_list: Option<Vec<xproto::Atom>>, pub type_list: Option<Vec<xproto::Atom>>,
// Populated by XdndPosition event handler // Populated by XdndPosition event handler
pub source_window: Option<xproto::Window>, pub source_window: Option<xproto::Window>,
// Populated by XdndPosition event handler
pub position: PhysicalPosition<f64>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler) // Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>, pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub dragging: bool,
} }
impl Dnd { impl Dnd {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> { pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
Ok(Dnd { Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None })
xconn,
version: None,
type_list: None,
source_window: None,
position: PhysicalPosition::default(),
result: None,
dragging: false,
})
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
@@ -73,7 +59,6 @@ impl Dnd {
self.type_list = None; self.type_list = None;
self.source_window = None; self.source_window = None;
self.result = None; self.result = None;
self.dragging = false;
} }
pub unsafe fn send_status( pub unsafe fn send_status(

File diff suppressed because it is too large Load Diff

View File

@@ -116,21 +116,25 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let mut new_contexts = HashMap::new(); let mut new_contexts = HashMap::new();
for (window, old_context) in unsafe { (*inner).contexts.iter() } { for (window, old_context) in unsafe { (*inner).contexts.iter() } {
let area = old_context.as_ref().map(|old_context| old_context.ic_area); let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
// Check if the IME was allowed on that context. // Check if the IME was allowed on that context.
let is_allowed = let is_allowed =
old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default(); old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
// We can't use the style from the old context here, since it may change on reload, so
// pick style from the new XIM based on the old state.
let style = if is_allowed { new_im.preedit_style } else { new_im.none_style };
let new_context = { let new_context = {
let result = unsafe { let result = unsafe {
ImeContext::new( ImeContext::new(
xconn, xconn,
&new_im, new_im.im,
style,
*window, *window,
area, spot,
(*inner).event_sender.clone(), (*inner).event_sender.clone(),
is_allowed,
) )
}; };
if result.is_err() { if result.is_err() {

View File

@@ -1,12 +1,13 @@
use std::error::Error; use std::error::Error;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::c_short;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, mem, ptr}; use std::{fmt, mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct}; use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use super::{ffi, util, XConnection, XError}; use super::{ffi, util, XConnection, XError};
use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle}; use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
/// IME creation error. /// IME creation error.
@@ -195,8 +196,8 @@ struct ImeContextClientData {
// through `ImeInner`. // through `ImeInner`.
pub struct ImeContext { pub struct ImeContext {
pub(crate) ic: ffi::XIC, pub(crate) ic: ffi::XIC,
pub(crate) ic_area: ffi::XRectangle, pub(crate) ic_spot: ffi::XPoint,
pub(crate) allowed: bool, pub(crate) style: Style,
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free // Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
// from there we keep the pointer to automatically deallocate it. // from there we keep the pointer to automatically deallocate it.
_client_data: Box<ImeContextClientData>, _client_data: Box<ImeContextClientData>,
@@ -205,11 +206,11 @@ pub struct ImeContext {
impl ImeContext { impl ImeContext {
pub(crate) unsafe fn new( pub(crate) unsafe fn new(
xconn: &Arc<XConnection>, xconn: &Arc<XConnection>,
im: &InputMethod, im: ffi::XIM,
style: Style,
window: ffi::Window, window: ffi::Window,
ic_area: Option<ffi::XRectangle>, ic_spot: Option<ffi::XPoint>,
event_sender: ImeEventSender, event_sender: ImeEventSender,
allowed: bool,
) -> Result<Self, ImeContextCreationError> { ) -> Result<Self, ImeContextCreationError> {
let client_data = Box::into_raw(Box::new(ImeContextClientData { let client_data = Box::into_raw(Box::new(ImeContextClientData {
window, window,
@@ -218,24 +219,20 @@ impl ImeContext {
cursor_pos: 0, cursor_pos: 0,
})); }));
let style = if allowed { im.preedit_style } else { im.none_style };
let ic = match style as _ { let ic = match style as _ {
Style::Preedit(style) => unsafe { Style::Preedit(style) => unsafe {
ImeContext::create_preedit_ic( ImeContext::create_preedit_ic(
xconn, xconn,
im.im, im,
style, style,
window, window,
client_data as ffi::XPointer, client_data as ffi::XPointer,
) )
}, },
Style::Nothing(style) => unsafe { Style::Nothing(style) => unsafe {
ImeContext::create_nothing_ic(xconn, im.im, style, window) ImeContext::create_nothing_ic(xconn, im, style, window)
},
Style::None(style) => unsafe {
ImeContext::create_none_ic(xconn, im.im, style, window)
}, },
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
} }
.ok_or(ImeContextCreationError::Null)?; .ok_or(ImeContextCreationError::Null)?;
@@ -243,14 +240,14 @@ impl ImeContext {
let mut context = ImeContext { let mut context = ImeContext {
ic, ic,
ic_area: ffi::XRectangle { x: 0, y: 0, width: 0, height: 0 }, ic_spot: ffi::XPoint { x: 0, y: 0 },
allowed, style,
_client_data: unsafe { Box::from_raw(client_data) }, _client_data: unsafe { Box::from_raw(client_data) },
}; };
// Set the preedit cursor area, if it's present. // Set the spot location, if it's present.
if let Some(ic_area) = ic_area { if let Some(ic_spot) = ic_spot {
context.set_area(xconn, ic_area.x, ic_area.y, ic_area.width, ic_area.height); context.set_spot(xconn, ic_spot.x, ic_spot.y)
} }
Ok(context) Ok(context)
@@ -351,34 +348,20 @@ impl ImeContext {
} }
pub fn is_allowed(&self) -> bool { pub fn is_allowed(&self) -> bool {
self.allowed !matches!(self.style, Style::None(_))
} }
/// Set the spot and area for preedit text. // Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
/// // are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
/// This functionality depends on the libx11 version. // window and couldn't be changed.
/// - Until libx11 1.8.2, XNSpotLocation was blocked by libx11 in On-The-Spot mode. //
/// - Until libx11 1.8.11, XNArea was blocked by libx11 in On-The-Spot mode. // For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
/// pub(crate) fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
/// Use of this information is discretionary by input method servers, if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
/// and some may not use it by default, even if they have support.
pub(crate) fn set_area(
&mut self,
xconn: &Arc<XConnection>,
x: i16,
y: i16,
width: u16,
height: u16,
) {
let ic_area = ffi::XRectangle { x, y, width, height };
if !self.is_allowed() || self.ic_area == ic_area {
return; return;
} }
self.ic_area = ic_area; self.ic_spot = ffi::XPoint { x, y };
let ic_spot =
ffi::XPoint { x: x.saturating_add(width as i16), y: y.saturating_add(height as i16) };
unsafe { unsafe {
let preedit_attr = util::memory::XSmartPointer::new( let preedit_attr = util::memory::XSmartPointer::new(
@@ -386,9 +369,7 @@ impl ImeContext {
(xconn.xlib.XVaCreateNestedList)( (xconn.xlib.XVaCreateNestedList)(
0, 0,
ffi::XNSpotLocation_0.as_ptr(), ffi::XNSpotLocation_0.as_ptr(),
&ic_spot, &self.ic_spot,
ffi::XNArea_0.as_ptr(),
&self.ic_area,
ptr::null_mut::<()>(), ptr::null_mut::<()>(),
), ),
) )

View File

@@ -177,7 +177,7 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
) )
.map_err(GetXimServersError::GetPropertyError)? .map_err(GetXimServersError::GetPropertyError)?
.into_iter() .into_iter()
.map(|atom| atom as _) .map(ffi::Atom::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len()); let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());

View File

@@ -5,18 +5,18 @@ mod context;
mod inner; mod inner;
mod input_method; mod input_method;
use std::fmt;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::debug;
use self::callbacks::*; use self::callbacks::*;
use self::context::ImeContext; use self::context::ImeContext;
pub use self::context::ImeContextCreationError; pub use self::context::ImeContextCreationError;
use self::inner::{close_im, ImeInner}; use self::inner::{close_im, ImeInner};
use self::input_method::PotentialInputMethods; use self::input_method::{PotentialInputMethods, Style};
use super::{ffi, util, XConnection, XError}; use super::{ffi, util, XConnection, XError};
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -36,8 +36,8 @@ pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
/// Request to control XIM handler from the window. /// Request to control XIM handler from the window.
pub enum ImeRequest { pub enum ImeRequest {
/// Set IME preedit area for given `window_id`. /// Set IME spot position for given `window_id`.
Area(ffi::Window, i16, i16, u16, u16), Position(ffi::Window, i16, i16),
/// Allow IME input for the given `window_id`. /// Allow IME input for the given `window_id`.
Allow(ffi::Window, bool), Allow(ffi::Window, bool),
@@ -57,12 +57,6 @@ pub(crate) struct Ime {
inner: Box<ImeInner>, inner: Box<ImeInner>,
} }
impl fmt::Debug for Ime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ime").finish_non_exhaustive()
}
}
impl Ime { impl Ime {
pub fn new( pub fn new(
xconn: Arc<XConnection>, xconn: Arc<XConnection>,
@@ -119,26 +113,39 @@ impl Ime {
pub fn create_context( pub fn create_context(
&mut self, &mut self,
window: ffi::Window, window: ffi::Window,
with_ime: bool, with_preedit: bool,
) -> Result<bool, ImeContextCreationError> { ) -> Result<bool, ImeContextCreationError> {
let context = if self.is_destroyed() { let context = if self.is_destroyed() {
// Create empty entry in map, so that when IME is rebuilt, this window has a context. // Create empty entry in map, so that when IME is rebuilt, this window has a context.
None None
} else { } else {
let im = self.inner.im.as_ref().unwrap(); let im = self.inner.im.as_ref().unwrap();
let style = if with_preedit { im.preedit_style } else { im.none_style };
let context = unsafe { let context = unsafe {
ImeContext::new( ImeContext::new(
&self.inner.xconn, &self.inner.xconn,
im, im.im,
style,
window, window,
None, None,
self.inner.event_sender.clone(), self.inner.event_sender.clone(),
with_ime,
)? )?
}; };
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled }; // Check the state on the context, since it could fail to enable or disable preedit.
let event = if matches!(style, Style::None(_)) {
if with_preedit {
debug!("failed to create IME context with preedit support.")
}
ImeEvent::Disabled
} else {
if !with_preedit {
debug!("failed to create IME context without preedit support.")
}
ImeEvent::Enabled
};
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event"); self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
Some(context) Some(context)
@@ -192,12 +199,12 @@ impl Ime {
} }
} }
pub fn send_xim_area(&mut self, window: ffi::Window, x: i16, y: i16, w: u16, h: u16) { pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
if self.is_destroyed() { if self.is_destroyed() {
return; return;
} }
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
context.set_area(&self.xconn, x as _, y as _, w as _, h as _); context.set_spot(&self.xconn, x as _, y as _);
} }
} }

View File

@@ -6,7 +6,7 @@ use std::ops::Deref;
use std::os::raw::*; use std::os::raw::*;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError}; use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Mutex, Weak}; use std::sync::{Arc, Weak};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::{fmt, mem, ptr, slice, str}; use std::{fmt, mem, ptr, slice, str};
@@ -24,21 +24,19 @@ use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError}; use crate::error::{EventLoopError, RequestError};
use crate::event::{DeviceId, StartCause, WindowEvent}; use crate::event::{DeviceId, Event, StartCause, WindowEvent};
use crate::event_loop::{ use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle, OwnedDisplayHandle as CoreOwnedDisplayHandle,
}; };
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::pump_events::PumpStatus; use crate::platform::pump_events::PumpStatus;
use crate::platform::x11::XlibErrorHook;
use crate::platform_impl::common::xkb::Context; use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::platform::min_timeout; use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::x11::window::Window; use crate::platform_impl::x11::window::Window;
use crate::utils::Lazy; use crate::platform_impl::PlatformCustomCursor;
use crate::window::{ use crate::window::{
CustomCursor as CoreCustomCursor, CustomCursorSource, Theme, Window as CoreWindow, CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as CoreWindow,
WindowAttributes, WindowId, WindowAttributes, WindowId,
}; };
@@ -73,62 +71,6 @@ type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
type X11Source = Generic<BorrowedFd<'static>>; type X11Source = Generic<BorrowedFd<'static>>;
#[cfg(x11_platform)]
pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
/// Hooks for X11 errors.
#[cfg(x11_platform)]
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
#[cfg(x11_platform)]
unsafe extern "C" fn x_error_callback(
display: *mut ffi::Display,
event: *mut ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
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<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
unsafe {
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
)
};
let description =
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
let error = unsafe {
XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
}
};
// Don't log error.
if !error_handled {
tracing::error!("X11 error: {:#?}", error);
// XXX only update the error, if it wasn't handled by any of the hooks.
*xconn.latest_error.lock().unwrap() = Some(error);
}
}
// Fun fact: this return value is completely ignored.
0
}
#[derive(Debug)]
struct WakeSender<T> { struct WakeSender<T> {
sender: Sender<T>, sender: Sender<T>,
waker: Ping, waker: Ping,
@@ -149,7 +91,6 @@ impl<T> WakeSender<T> {
} }
} }
#[derive(Debug)]
struct PeekableReceiver<T> { struct PeekableReceiver<T> {
recv: Receiver<T>, recv: Receiver<T>,
first: Option<T>, first: Option<T>,
@@ -186,7 +127,6 @@ impl<T> PeekableReceiver<T> {
} }
} }
#[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
xconn: Arc<XConnection>, xconn: Arc<XConnection>,
wm_delete_window: xproto::Atom, wm_delete_window: xproto::Atom,
@@ -204,7 +144,6 @@ pub struct ActiveEventLoop {
device_events: Cell<DeviceEvents>, device_events: Cell<DeviceEvents>,
} }
#[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
loop_running: bool, loop_running: bool,
event_loop: Loop<'static, EventLoopState>, event_loop: Loop<'static, EventLoopState>,
@@ -218,7 +157,6 @@ pub struct EventLoop {
type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial); type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial);
#[derive(Debug)]
struct EventLoopState { struct EventLoopState {
/// The latest readiness state for the x11 file descriptor /// The latest readiness state for the x11 file descriptor
x11_readiness: Readiness, x11_readiness: Readiness,
@@ -228,12 +166,7 @@ struct EventLoopState {
} }
impl EventLoop { impl EventLoop {
pub(crate) fn new() -> Result<EventLoop, EventLoopError> { pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop {
let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(os_error!(err.clone()).into()),
};
let root = xconn.default_root().root; let root = xconn.default_root().root;
let atoms = xconn.atoms(); let atoms = xconn.atoms();
@@ -426,16 +359,14 @@ impl EventLoop {
event_processor.init_device(ALL_DEVICES); event_processor.init_device(ALL_DEVICES);
let event_loop = EventLoop { EventLoop {
loop_running: false, loop_running: false,
event_loop, event_loop,
event_processor, event_processor,
redraw_receiver: PeekableReceiver::from_recv(redraw_channel), redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
activation_receiver: PeekableReceiver::from_recv(activation_token_channel), activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
state: EventLoopState { x11_readiness: Readiness::EMPTY, proxy_wake_up: false }, state: EventLoopState { x11_readiness: Readiness::EMPTY, proxy_wake_up: false },
}; }
Ok(event_loop)
} }
pub(crate) fn window_target(&self) -> &dyn RootActiveEventLoop { pub(crate) fn window_target(&self) -> &dyn RootActiveEventLoop {
@@ -498,6 +429,8 @@ impl EventLoop {
if let Some(code) = self.exit_code() { if let Some(code) = self.exit_code() {
self.loop_running = false; self.loop_running = false;
app.exiting(self.window_target());
PumpStatus::Exit(code) PumpStatus::Exit(code)
} else { } else {
PumpStatus::Continue PumpStatus::Continue
@@ -568,7 +501,6 @@ impl EventLoop {
// If we don't have any pending `_receiver` // If we don't have any pending `_receiver`
if !self.has_pending() if !self.has_pending()
&& !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll) && !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll)
&& timeout.is_none()
{ {
return; return;
} }
@@ -600,7 +532,7 @@ impl EventLoop {
Some(Ok(token)) => { Some(Ok(token)) => {
let event = WindowEvent::ActivationTokenDone { let event = WindowEvent::ActivationTokenDone {
serial, serial,
token: crate::window::ActivationToken::from_raw(token), token: crate::window::ActivationToken::_new(token),
}; };
app.window_event(&self.event_processor.target, window_id, event); app.window_event(&self.event_processor.target, window_id, event);
}, },
@@ -642,7 +574,22 @@ impl EventLoop {
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() }; let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, app); self.event_processor.process_event(&mut xev, |window_target, event: Event| {
if let Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } = event
{
window_target.redraw_sender.send(window_id);
} else {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}
});
} }
} }
@@ -729,22 +676,29 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor( fn create_custom_cursor(
&self, &self,
custom_cursor: CustomCursorSource, custom_cursor: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> { ) -> Result<RootCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(self, custom_cursor)?))) Ok(RootCustomCursor {
inner: PlatformCustomCursor::X(CustomCursor::new(self, custom_cursor.inner)?),
})
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
Box::new( Box::new(
self.xconn self.xconn
.available_monitors() .available_monitors()
.into_iter() .into_iter()
.flatten() .flatten()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))), .map(crate::platform_impl::MonitorHandle::X)
.map(|inner| crate::monitor::MonitorHandle { inner }),
) )
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
self.xconn.primary_monitor().ok().map(|monitor| CoreMonitorHandle(Arc::new(monitor))) self.xconn
.primary_monitor()
.ok()
.map(crate::platform_impl::MonitorHandle::X)
.map(|inner| crate::monitor::MonitorHandle { inner })
} }
fn system_theme(&self) -> Option<Theme> { fn system_theme(&self) -> Option<Theme> {
@@ -823,7 +777,7 @@ impl Deref for DeviceInfo<'_> {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct EventLoopProxy { pub struct EventLoopProxy {
ping: Ping, ping: Ping,
} }

View File

@@ -1,12 +1,12 @@
use std::num::NonZeroU32; use std::num::{NonZeroU16, NonZeroU32};
use x11rb::connection::RequestConnection; use x11rb::connection::RequestConnection;
use x11rb::protocol::randr::{self, ConnectionExt as _}; use x11rb::protocol::randr::{self, ConnectionExt as _};
use x11rb::protocol::xproto; use x11rb::protocol::xproto;
use super::{util, X11Error, XConnection}; use super::{util, X11Error, XConnection};
use crate::dpi::PhysicalPosition; use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandleProvider, VideoMode}; use crate::platform_impl::VideoModeHandle as PlatformVideoModeHandle;
// Used for testing. This should always be committed as false. // Used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false; const DISABLE_MONITOR_LIST_CACHING: bool = false;
@@ -21,13 +21,32 @@ impl XConnection {
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
pub(crate) current: bool, pub(crate) current: bool,
pub(crate) mode: VideoMode, pub(crate) size: (u32, u32),
pub(crate) bit_depth: Option<NonZeroU16>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) native_mode: randr::Mode, pub(crate) native_mode: randr::Mode,
pub(crate) monitor: Option<MonitorHandle>,
} }
impl From<VideoModeHandle> for VideoMode { impl VideoModeHandle {
fn from(handle: VideoModeHandle) -> Self { #[inline]
handle.mode pub fn size(&self) -> PhysicalSize<u32> {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
#[inline]
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone().unwrap()
} }
} }
@@ -46,37 +65,7 @@ pub struct MonitorHandle {
/// Used to determine which windows are on this monitor /// Used to determine which windows are on this monitor
pub(crate) rect: util::AaRect, pub(crate) rect: util::AaRect,
/// Supported video modes on this monitor /// Supported video modes on this monitor
pub(crate) video_modes: Vec<VideoModeHandle>, video_modes: Vec<VideoModeHandle>,
}
impl MonitorHandleProvider for MonitorHandle {
fn id(&self) -> u128 {
self.native_id() as _
}
fn native_id(&self) -> u64 {
self.id as _
}
fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
Some(self.name.as_str().into())
}
fn position(&self) -> Option<PhysicalPosition<i32>> {
Some(self.position.into())
}
fn scale_factor(&self) -> f64 {
self.scale_factor
}
fn current_video_mode(&self) -> Option<VideoMode> {
self.video_modes.iter().find_map(|mode| mode.current.then(|| mode.clone().into()))
}
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
Box::new(self.video_modes.clone().into_iter().map(|mode| mode.into()))
}
} }
impl PartialEq for MonitorHandle { impl PartialEq for MonitorHandle {
@@ -150,6 +139,38 @@ impl MonitorHandle {
// Zero is an invalid XID value; no real monitor will have it // Zero is an invalid XID value; no real monitor will have it
self.id == 0 self.id == 0
} }
pub fn name(&self) -> Option<String> {
Some(self.name.clone())
}
#[inline]
pub fn native_identifier(&self) -> u32 {
self.id as _
}
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
Some(self.position.into())
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.scale_factor
}
#[inline]
pub fn current_video_mode(&self) -> Option<PlatformVideoModeHandle> {
self.video_modes.iter().find(|mode| mode.current).cloned().map(PlatformVideoModeHandle::X)
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoModeHandle> {
let monitor = self.clone();
self.video_modes.clone().into_iter().map(move |mut x| {
x.monitor = Some(monitor.clone());
PlatformVideoModeHandle::X(x)
})
}
} }
impl XConnection { impl XConnection {

View File

@@ -9,8 +9,8 @@ use x11rb::protocol::xproto;
use super::super::ActiveEventLoop; use super::super::ActiveEventLoop;
use super::*; use super::*;
use crate::cursor::{CustomCursorProvider, CustomCursorSource}; use crate::error::RequestError;
use crate::error::{NotSupportedError, RequestError}; use crate::platform_impl::PlatformCustomCursorSource;
use crate::window::CursorIcon; use crate::window::CursorIcon;
impl XConnection { impl XConnection {
@@ -36,7 +36,7 @@ impl XConnection {
window: xproto::Window, window: xproto::Window,
cursor: &CustomCursor, cursor: &CustomCursor,
) -> Result<(), X11Error> { ) -> Result<(), X11Error> {
self.update_cursor(window, cursor.cursor) self.update_cursor(window, cursor.inner.cursor)
} }
/// Create a cursor from an image. /// Create a cursor from an image.
@@ -170,45 +170,32 @@ pub enum SelectedCursor {
Named(CursorIcon), Named(CursorIcon),
} }
impl Default for SelectedCursor {
fn default() -> Self {
SelectedCursor::Named(Default::default())
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CustomCursor { pub struct CustomCursor {
xconn: Arc<XConnection>, inner: Arc<CustomCursorInner>,
cursor: xproto::Cursor,
} }
impl Hash for CustomCursor { impl Hash for CustomCursor {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.cursor.hash(state); Arc::as_ptr(&self.inner).hash(state);
} }
} }
impl PartialEq for CustomCursor { impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.cursor == other.cursor Arc::ptr_eq(&self.inner, &other.inner)
} }
} }
impl Eq for CustomCursor {} impl Eq for CustomCursor {}
impl CustomCursor { impl CustomCursor {
pub(crate) fn new( pub(crate) fn new(
event_loop: &ActiveEventLoop, event_loop: &ActiveEventLoop,
cursor: CustomCursorSource, mut cursor: PlatformCustomCursorSource,
) -> Result<CustomCursor, RequestError> { ) -> Result<CustomCursor, RequestError> {
let mut cursor = match cursor {
CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
// Reverse RGBA order to BGRA. // Reverse RGBA order to BGRA.
cursor.rgba.chunks_mut(4).for_each(|chunk| { cursor.0.rgba.chunks_mut(4).for_each(|chunk| {
let chunk: &mut [u8; 4] = chunk.try_into().unwrap(); let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
chunk[0..3].reverse(); chunk[0..3].reverse();
@@ -222,26 +209,32 @@ impl CustomCursor {
let cursor = event_loop let cursor = event_loop
.xconn .xconn
.create_cursor_from_image( .create_cursor_from_image(
cursor.width, cursor.0.width,
cursor.height, cursor.0.height,
cursor.hotspot_x, cursor.0.hotspot_x,
cursor.hotspot_y, cursor.0.hotspot_y,
&cursor.rgba, &cursor.0.rgba,
) )
.map_err(|err| os_error!(err))?; .map_err(|err| os_error!(err))?;
Ok(Self { xconn: event_loop.xconn.clone(), cursor }) Ok(Self { inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }) })
} }
} }
impl Drop for CustomCursor { #[derive(Debug)]
struct CustomCursorInner {
xconn: Arc<XConnection>,
cursor: xproto::Cursor,
}
impl Drop for CustomCursorInner {
fn drop(&mut self) { fn drop(&mut self) {
self.xconn.xcb_connection().free_cursor(self.cursor).map(|r| r.ignore_error()).ok(); self.xconn.xcb_connection().free_cursor(self.cursor).map(|r| r.ignore_error()).ok();
} }
} }
impl CustomCursorProvider for CustomCursor { impl Default for SelectedCursor {
fn is_animated(&self) -> bool { fn default() -> Self {
false SelectedCursor::Named(Default::default())
} }
} }

View File

@@ -95,7 +95,7 @@ impl FrameExtentsHeuristic {
impl XConnection { impl XConnection {
// This is adequate for inner_position // This is adequate for inner_position
pub fn translate_coords_root( pub fn translate_coords(
&self, &self,
window: xproto::Window, window: xproto::Window,
root: xproto::Window, root: xproto::Window,
@@ -103,19 +103,6 @@ impl XConnection {
self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into) self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into)
} }
pub fn translate_coords(
&self,
src_w: xproto::Window,
dst_w: xproto::Window,
src_x: i16,
src_y: i16,
) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
self.xcb_connection()
.translate_coordinates(src_w, dst_w, src_x, src_y)?
.reply()
.map_err(Into::into)
}
// This is adequate for surface_size // This is adequate for surface_size
pub fn get_geometry( pub fn get_geometry(
&self, &self,
@@ -202,7 +189,7 @@ impl XConnection {
// that, fullscreen windows often aren't nested. // that, fullscreen windows often aren't nested.
let (inner_y_rel_root, child) = { let (inner_y_rel_root, child) = {
let coords = self let coords = self
.translate_coords_root(window, root) .translate_coords(window, root)
.expect("Failed to translate window coordinates"); .expect("Failed to translate window coordinates");
(coords.dst_y, coords.child) (coords.dst_y, coords.child)
}; };

View File

@@ -7,7 +7,6 @@ use x11rb::protocol::randr::{self, ConnectionExt as _};
use super::*; use super::*;
use crate::dpi::validate_scale_factor; use crate::dpi::validate_scale_factor;
use crate::monitor::VideoMode;
use crate::platform_impl::platform::x11::{monitor, VideoModeHandle}; use crate::platform_impl::platform::x11::{monitor, VideoModeHandle};
/// Represents values of `WINIT_HIDPI_FACTOR`. /// Represents values of `WINIT_HIDPI_FACTOR`.
@@ -83,14 +82,17 @@ impl XConnection {
// XRROutputInfo contains an array of mode ids that correspond to // XRROutputInfo contains an array of mode ids that correspond to
// modes in the array in XRRScreenResources // modes in the array in XRRScreenResources
.filter(|x| output_modes.iter().any(|id| x.id == *id)) .filter(|x| output_modes.iter().any(|id| x.id == *id))
.map(|mode| VideoModeHandle { .map(|mode| {
current: mode.id == current_mode, VideoModeHandle {
mode: VideoMode { current: mode.id == current_mode,
size: (mode.width as u32, mode.height as u32).into(), size: (mode.width.into(), mode.height.into()),
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode), refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode),
bit_depth: NonZeroU16::new(bit_depth as u16), bit_depth: NonZeroU16::new(bit_depth as u16),
}, native_mode: mode.id,
native_mode: mode.id, // This is populated in `MonitorHandle::video_modes` as the
// video mode is returned to the user
monitor: None,
}
}) })
.collect(); .collect();

View File

@@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::ffi::CString; use std::ffi::CString;
use std::mem::replace; use std::mem::replace;
use std::num::NonZeroU32; use std::num::NonZeroU32;
@@ -19,31 +18,27 @@ use x11rb::protocol::{randr, xinput};
use super::util::{self, SelectedCursor}; use super::util::{self, SelectedCursor};
use super::{ use super::{
ffi, ActiveEventLoop, CookieResultExt, CustomCursor, ImeRequest, ImeSender, VoidCookie, ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, XConnection,
XConnection,
}; };
use crate::application::ApplicationHandler; use crate::cursor::{Cursor, CustomCursor as RootCustomCursor};
use crate::cursor::Cursor;
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError}; use crate::error::{NotSupportedError, RequestError};
use crate::event::{SurfaceSizeWriter, WindowEvent}; use crate::event::{Event, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::AsyncRequestSerial; use crate::event_loop::AsyncRequestSerial;
use crate::icon::RgbaIcon;
use crate::monitor::{
Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider, VideoMode,
};
use crate::platform::x11::WindowType; use crate::platform::x11::WindowType;
use crate::platform_impl::common;
use crate::platform_impl::x11::atoms::*; use crate::platform_impl::x11::atoms::*;
use crate::platform_impl::x11::{ use crate::platform_impl::x11::{
xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error,
}; };
use crate::platform_impl::{
common, Fullscreen, MonitorHandle as PlatformMonitorHandle, PlatformCustomCursor, PlatformIcon,
VideoModeHandle as PlatformVideoModeHandle,
};
use crate::window::{ use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowAttributes, WindowButtons, WindowId, WindowLevel,
}; };
#[derive(Debug)]
pub(crate) struct Window(Arc<UnownedWindow>); pub(crate) struct Window(Arc<UnownedWindow>);
impl Deref for Window { impl Deref for Window {
@@ -183,12 +178,12 @@ impl CoreWindow for Window {
self.0.is_maximized() self.0.is_maximized()
} }
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) { fn set_fullscreen(&self, fullscreen: Option<crate::window::Fullscreen>) {
self.0.set_fullscreen(fullscreen) self.0.set_fullscreen(fullscreen.map(Into::into))
} }
fn fullscreen(&self) -> Option<Fullscreen> { fn fullscreen(&self) -> Option<crate::window::Fullscreen> {
self.0.fullscreen() self.0.fullscreen().map(Into::into)
} }
fn set_decorations(&self, decorations: bool) { fn set_decorations(&self, decorations: bool) {
@@ -204,11 +199,7 @@ impl CoreWindow for Window {
} }
fn set_window_icon(&self, window_icon: Option<crate::window::Icon>) { fn set_window_icon(&self, window_icon: Option<crate::window::Icon>) {
let icon = match window_icon.as_ref() { self.0.set_window_icon(window_icon.map(|inner| inner.inner))
Some(icon) => icon.0.cast_ref::<RgbaIcon>(),
None => None,
};
self.0.set_window_icon(icon)
} }
fn set_ime_cursor_area(&self, position: Position, size: Size) { fn set_ime_cursor_area(&self, position: Position, size: Size) {
@@ -283,21 +274,28 @@ impl CoreWindow for Window {
self.0.set_cursor_hittest(hittest) self.0.set_cursor_hittest(hittest)
} }
fn current_monitor(&self) -> Option<CoreMonitorHandle> { fn current_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
self.0.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor))) self.0
.current_monitor()
.map(crate::platform_impl::MonitorHandle::X)
.map(|inner| crate::monitor::MonitorHandle { inner })
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
Box::new( Box::new(
self.0 self.0
.available_monitors() .available_monitors()
.into_iter() .into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))), .map(crate::platform_impl::MonitorHandle::X)
.map(|inner| crate::monitor::MonitorHandle { inner }),
) )
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
self.0.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor))) self.0
.primary_monitor()
.map(crate::platform_impl::MonitorHandle::X)
.map(|inner| crate::monitor::MonitorHandle { inner })
} }
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle { fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
@@ -329,7 +327,7 @@ impl Drop for Window {
let xconn = &window.xconn; let xconn = &window.xconn;
// Restore the video mode on drop. // Restore the video mode on drop.
if let Some(Fullscreen::Exclusive(..)) = window.fullscreen() { if let Some(Fullscreen::Exclusive(_)) = window.fullscreen() {
window.set_fullscreen(None); window.set_fullscreen(None);
} }
@@ -413,7 +411,6 @@ impl SharedState {
unsafe impl Send for UnownedWindow {} unsafe impl Send for UnownedWindow {}
unsafe impl Sync for UnownedWindow {} unsafe impl Sync for UnownedWindow {}
#[derive(Debug)]
pub struct UnownedWindow { pub struct UnownedWindow {
pub(crate) xconn: Arc<XConnection>, // never changes pub(crate) xconn: Arc<XConnection>, // never changes
xwindow: xproto::Window, // never changes xwindow: xproto::Window, // never changes
@@ -770,10 +767,8 @@ impl UnownedWindow {
.check()); .check());
// Set window icons // Set window icons
if let Some(icon) = if let Some(icon) = window_attrs.window_icon {
window_attrs.window_icon.as_ref().and_then(|icon| icon.0.cast_ref::<RgbaIcon>()) leap!(window.set_icon_inner(icon.inner)).ignore_error();
{
leap!(window.set_icon_inner(icon)).ignore_error();
} }
// Opt into handling window close and resize synchronization // Opt into handling window close and resize synchronization
@@ -865,7 +860,8 @@ impl UnownedWindow {
if window_attrs.fullscreen.is_some() { if window_attrs.fullscreen.is_some() {
if let Some(flusher) = if let Some(flusher) =
leap!(window.set_fullscreen_inner(window_attrs.fullscreen.clone())) leap!(window
.set_fullscreen_inner(window_attrs.fullscreen.clone().map(Into::into)))
{ {
flusher.ignore_error() flusher.ignore_error()
} }
@@ -884,7 +880,7 @@ impl UnownedWindow {
// Remove the startup notification if we have one. // Remove the startup notification if we have one.
if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() { if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() {
leap!(xconn.remove_activation_token(xwindow, &startup.token)); leap!(xconn.remove_activation_token(xwindow, &startup._token));
} }
// We never want to give the user a broken window, since by then, it's too late to handle. // We never want to give the user a broken window, since by then, it's too late to handle.
@@ -1039,17 +1035,20 @@ impl UnownedWindow {
// fullscreen, so we can restore it upon exit, as XRandR does not // fullscreen, so we can restore it upon exit, as XRandR does not
// provide a mechanism to set this per app-session or restore this // provide a mechanism to set this per app-session or restore this
// to the desktop video mode as macOS and Windows do // to the desktop video mode as macOS and Windows do
(&None, &Some(Fullscreen::Exclusive(ref monitor, _))) (&None, &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))))
| (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref monitor, _))) => { | (
let id = monitor.native_id() as _; &Some(Fullscreen::Borderless(_)),
&Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))),
) => {
let monitor = video_mode.monitor.as_ref().unwrap();
shared_state_lock.desktop_video_mode = Some(( shared_state_lock.desktop_video_mode = Some((
id, monitor.id,
self.xconn.get_crtc_mode(id).expect("Failed to get desktop video mode"), self.xconn.get_crtc_mode(monitor.id).expect("Failed to get desktop video mode"),
)); ));
}, },
// Restore desktop video mode upon exiting exclusive fullscreen // Restore desktop video mode upon exiting exclusive fullscreen
(&Some(Fullscreen::Exclusive(..)), &None) (&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(..)), &Some(Fullscreen::Borderless(_))) => { | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap(); let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
self.xconn self.xconn
.set_crtc_config(monitor_id, mode_id) .set_crtc_config(monitor_id, mode_id)
@@ -1072,35 +1071,26 @@ impl UnownedWindow {
flusher.map(Some) flusher.map(Some)
}, },
Some(fullscreen) => { Some(fullscreen) => {
let (monitor, video_mode): (Cow<'_, X11MonitorHandle>, Option<&VideoMode>) = let (video_mode, monitor) = match fullscreen {
match &fullscreen { Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)) => {
Fullscreen::Exclusive(monitor, video_mode) => { (Some(video_mode), video_mode.monitor.clone().unwrap())
let monitor = monitor.cast_ref::<X11MonitorHandle>().unwrap(); },
(Cow::Borrowed(monitor), Some(video_mode)) Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => {
}, (None, monitor)
Fullscreen::Borderless(Some(monitor)) => { },
let monitor = monitor.cast_ref::<X11MonitorHandle>().unwrap(); Fullscreen::Borderless(None) => {
(Cow::Borrowed(monitor), None) (None, self.shared_state_lock().last_monitor.clone())
}, },
Fullscreen::Borderless(None) => { #[cfg(wayland_platform)]
(Cow::Owned(self.shared_state_lock().last_monitor.clone()), None) _ => unreachable!(),
}, };
};
// Don't set fullscreen on an invalid dummy monitor handle // Don't set fullscreen on an invalid dummy monitor handle
if monitor.is_dummy() { if monitor.is_dummy() {
return Ok(None); return Ok(None);
} }
if let Some(native_mode) = video_mode.and_then(|requested| { if let Some(video_mode) = video_mode {
monitor.video_modes.iter().find_map(|mode| {
if &mode.mode == requested {
Some(mode.native_mode)
} else {
None
}
})
}) {
// FIXME: this is actually not correct if we're setting the // FIXME: this is actually not correct if we're setting the
// video mode to a resolution higher than the current // video mode to a resolution higher than the current
// desktop resolution, because XRandR does not automatically // desktop resolution, because XRandR does not automatically
@@ -1127,7 +1117,7 @@ impl UnownedWindow {
// this will make someone unhappy, but it's very unusual for // this will make someone unhappy, but it's very unusual for
// games to want to do this anyway). // games to want to do this anyway).
self.xconn self.xconn
.set_crtc_config(monitor.native_id() as _, native_mode) .set_crtc_config(monitor.id, video_mode.native_mode)
.expect("failed to set video mode"); .expect("failed to set video mode");
} }
@@ -1217,8 +1207,7 @@ impl UnownedWindow {
&self, &self,
new_monitor: &X11MonitorHandle, new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>, maybe_prev_scale_factor: Option<f64>,
app: &mut dyn ApplicationHandler, mut callback: impl FnMut(Event),
event_loop: &ActiveEventLoop,
) { ) {
// Check if the self is on this monitor // Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone(); let monitor = self.shared_state_lock().last_monitor.clone();
@@ -1238,9 +1227,12 @@ impl UnownedWindow {
let old_surface_size = PhysicalSize::new(width, height); let old_surface_size = PhysicalSize::new(width, height);
let surface_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height))); let surface_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
app.window_event(event_loop, self.id(), WindowEvent::ScaleFactorChanged { callback(Event::WindowEvent {
scale_factor: new_monitor.scale_factor, window_id: self.id(),
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)), event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
},
}); });
let new_surface_size = *surface_size.lock().unwrap(); let new_surface_size = *surface_size.lock().unwrap();
@@ -1411,7 +1403,7 @@ impl UnownedWindow {
self.xconn.flush_requests().expect("Failed to set window-level state"); self.xconn.flush_requests().expect("Failed to set window-level state");
} }
fn set_icon_inner(&self, icon: &RgbaIcon) -> Result<VoidCookie<'_>, X11Error> { fn set_icon_inner(&self, icon: PlatformIcon) -> Result<VoidCookie<'_>, X11Error> {
let atoms = self.xconn.atoms(); let atoms = self.xconn.atoms();
let icon_atom = atoms[_NET_WM_ICON]; let icon_atom = atoms[_NET_WM_ICON];
let data = icon.to_cardinals(); let data = icon.to_cardinals();
@@ -1438,7 +1430,7 @@ impl UnownedWindow {
} }
#[inline] #[inline]
pub(crate) fn set_window_icon(&self, icon: Option<&RgbaIcon>) { pub(crate) fn set_window_icon(&self, icon: Option<PlatformIcon>) {
match icon { match icon {
Some(icon) => self.set_icon_inner(icon), Some(icon) => self.set_icon_inner(icon),
None => self.unset_icon_inner(), None => self.unset_icon_inner(),
@@ -1524,7 +1516,7 @@ impl UnownedWindow {
// This should be okay to unwrap since the only error XTranslateCoordinates can return // This should be okay to unwrap since the only error XTranslateCoordinates can return
// is BadWindow, and if the window handle is bad we have bigger problems. // is BadWindow, and if the window handle is bad we have bigger problems.
self.xconn self.xconn
.translate_coords_root(self.xwindow, self.root) .translate_coords(self.xwindow, self.root)
.map(|coords| (coords.dst_x.into(), coords.dst_y.into())) .map(|coords| (coords.dst_x.into(), coords.dst_y.into()))
.unwrap() .unwrap()
} }
@@ -1816,34 +1808,25 @@ impl UnownedWindow {
} }
} }
}, },
Cursor::Custom(cursor) => { Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::X(cursor) }) => {
let cursor = match cursor.cast_ref::<CustomCursor>() {
Some(cursor) => cursor,
None => {
tracing::error!("unrecognized cursor passed to X11 backend");
return;
},
};
#[allow(clippy::mutex_atomic)] #[allow(clippy::mutex_atomic)]
if *self.cursor_visible.lock().unwrap() { if *self.cursor_visible.lock().unwrap() {
if let Err(err) = self.xconn.set_custom_cursor(self.xwindow, cursor) { if let Err(err) = self.xconn.set_custom_cursor(self.xwindow, &cursor) {
tracing::error!("failed to set window icon: {err}"); tracing::error!("failed to set window icon: {err}");
} }
} }
*self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor.clone()); *self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor);
},
#[cfg(wayland_platform)]
Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::Wayland(_) }) => {
tracing::error!("passed a Wayland cursor to X11 backend")
}, },
} }
} }
#[inline] #[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> { pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
// We don't support the locked cursor yet, so ignore it early on.
if mode == CursorGrabMode::Locked {
return Err(NotSupportedError::new("locked cursor is not implemented on X11").into());
}
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
if mode == *grabbed_lock { if mode == *grabbed_lock {
return Ok(()); return Ok(());
@@ -1855,7 +1838,6 @@ impl UnownedWindow {
.xcb_connection() .xcb_connection()
.ungrab_pointer(x11rb::CURRENT_TIME) .ungrab_pointer(x11rb::CURRENT_TIME)
.expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`"); .expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`");
*grabbed_lock = CursorGrabMode::None;
let result = match mode { let result = match mode {
CursorGrabMode::None => self CursorGrabMode::None => self
@@ -1863,33 +1845,34 @@ impl UnownedWindow {
.flush_requests() .flush_requests()
.map_err(|err| RequestError::Os(os_error!(X11Error::Xlib(err)))), .map_err(|err| RequestError::Os(os_error!(X11Error::Xlib(err)))),
CursorGrabMode::Confined => { CursorGrabMode::Confined => {
let result = self let result = {
.xconn self.xconn
.xcb_connection() .xcb_connection()
.grab_pointer( .grab_pointer(
true as _, true as _,
self.xwindow, self.xwindow,
xproto::EventMask::BUTTON_PRESS xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE | xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::ENTER_WINDOW | xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW | xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::POINTER_MOTION | xproto::EventMask::POINTER_MOTION
| xproto::EventMask::POINTER_MOTION_HINT | xproto::EventMask::POINTER_MOTION_HINT
| xproto::EventMask::BUTTON1_MOTION | xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION | xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION | xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION | xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION | xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::KEYMAP_STATE, | xproto::EventMask::KEYMAP_STATE,
xproto::GrabMode::ASYNC, xproto::GrabMode::ASYNC,
xproto::GrabMode::ASYNC, xproto::GrabMode::ASYNC,
self.xwindow, self.xwindow,
0u32, 0u32,
x11rb::CURRENT_TIME, x11rb::CURRENT_TIME,
) )
.expect("Failed to call `grab_pointer`") .expect("Failed to call `grab_pointer`")
.reply() .reply()
.expect("Failed to receive reply from `grab_pointer`"); .expect("Failed to receive reply from `grab_pointer`")
};
match result.status { match result.status {
xproto::GrabStatus::SUCCESS => Ok(()), xproto::GrabStatus::SUCCESS => Ok(()),
@@ -1909,7 +1892,11 @@ impl UnownedWindow {
} }
.map_err(|err| RequestError::Os(os_error!(err))) .map_err(|err| RequestError::Os(os_error!(err)))
}, },
CursorGrabMode::Locked => return Ok(()), CursorGrabMode::Locked => {
return Err(
NotSupportedError::new("locked cursor is not implemented on X11").into()
);
},
}; };
if result.is_ok() { if result.is_ok() {
@@ -2060,13 +2047,17 @@ impl UnownedWindow {
#[inline] #[inline]
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) { pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
let PhysicalPosition { x, y } = spot.to_physical::<i16>(self.scale_factor()); let PhysicalPosition { x, y } = spot.to_physical::<i16>(self.scale_factor());
let PhysicalSize { width, height } = size.to_physical::<u16>(self.scale_factor()); let PhysicalSize { width, height } = size.to_physical::<i16>(self.scale_factor());
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Area( // We only currently support reporting a caret position via XIM.
// No IM servers currently process preedit area information from XIM clients
// and it is unclear this is even part of the standard protocol.
// Fcitx and iBus both assume that the position reported is at the insertion
// caret, and by default will place the candidate window under and to the
// right of the reported point.
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position(
self.xwindow as ffi::Window, self.xwindow as ffi::Window,
x, x.saturating_add(width),
y, y.saturating_add(height),
width,
height,
)); ));
} }

View File

@@ -1,3 +1,6 @@
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle};
use crate::window::Fullscreen as RootFullscreen;
#[cfg(android_platform)] #[cfg(android_platform)]
mod android; mod android;
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
@@ -26,6 +29,38 @@ use self::web as platform;
#[cfg(windows_platform)] #[cfg(windows_platform)]
use self::windows as platform; use self::windows as platform;
/// Helper for converting between platform-specific and generic
/// [`VideoModeHandle`]/[`MonitorHandle`]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Fullscreen {
Exclusive(VideoModeHandle),
Borderless(Option<MonitorHandle>),
}
impl From<RootFullscreen> for Fullscreen {
fn from(f: RootFullscreen) -> Self {
match f {
RootFullscreen::Exclusive(mode) => Self::Exclusive(mode.video_mode),
RootFullscreen::Borderless(Some(handle)) => Self::Borderless(Some(handle.inner)),
RootFullscreen::Borderless(None) => Self::Borderless(None),
}
}
}
impl From<Fullscreen> for RootFullscreen {
fn from(f: Fullscreen) -> Self {
match f {
Fullscreen::Exclusive(video_mode) => {
Self::Exclusive(RootVideoModeHandle { video_mode })
},
Fullscreen::Borderless(Some(inner)) => {
Self::Borderless(Some(RootMonitorHandle { inner }))
},
Fullscreen::Borderless(None) => Self::Borderless(None),
}
}
}
#[cfg(all( #[cfg(all(
not(ios_platform), not(ios_platform),
not(windows_platform), not(windows_platform),

View File

@@ -2,7 +2,7 @@ use std::cell::Cell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::{mpsc, Arc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use std::{iter, mem, slice}; use std::{mem, slice};
use bitflags::bitflags; use bitflags::bitflags;
use orbclient::{ use orbclient::{
@@ -11,7 +11,10 @@ use orbclient::{
}; };
use smol_str::SmolStr; use smol_str::SmolStr;
use super::{PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, WindowProperties}; use super::{
KeyEventExtra, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket,
WindowProperties,
};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError}; use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{self, Ime, Modifiers, StartCause}; use crate::event::{self, Ime, Modifiers, StartCause};
@@ -118,8 +121,8 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)), orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)),
orbclient::K_SEMICOLON => (KeyCode::Semicolon, None), orbclient::K_SEMICOLON => (KeyCode::Semicolon, None),
orbclient::K_SLASH => (KeyCode::Slash, None), orbclient::K_SLASH => (KeyCode::Slash, None),
orbclient::K_SPACE => (KeyCode::Space, None), orbclient::K_SPACE => (KeyCode::Space, Some(NamedKey::Space)),
orbclient::K_SUPER => (KeyCode::MetaLeft, Some(NamedKey::Meta)), orbclient::K_SUPER => (KeyCode::SuperLeft, Some(NamedKey::Super)),
orbclient::K_TAB => (KeyCode::Tab, Some(NamedKey::Tab)), orbclient::K_TAB => (KeyCode::Tab, Some(NamedKey::Tab)),
orbclient::K_TICK => (KeyCode::Backquote, None), orbclient::K_TICK => (KeyCode::Backquote, None),
orbclient::K_UP => (KeyCode::ArrowUp, Some(NamedKey::ArrowUp)), orbclient::K_UP => (KeyCode::ArrowUp, Some(NamedKey::ArrowUp)),
@@ -149,8 +152,8 @@ bitflags! {
const RCTRL = 1 << 3; const RCTRL = 1 << 3;
const LALT = 1 << 4; const LALT = 1 << 4;
const RALT = 1 << 5; const RALT = 1 << 5;
const LMETA = 1 << 6; const LSUPER = 1 << 6;
const RMETA = 1 << 7; const RSUPER = 1 << 7;
} }
} }
@@ -163,7 +166,7 @@ bitflags! {
} }
} }
#[derive(Default, Debug)] #[derive(Default)]
struct EventState { struct EventState {
keyboard: KeyboardModifierState, keyboard: KeyboardModifierState,
mouse: MouseButtonState, mouse: MouseButtonState,
@@ -200,8 +203,8 @@ impl EventState {
KeyCode::ControlRight => self.keyboard.set(KeyboardModifierState::RCTRL, pressed), KeyCode::ControlRight => self.keyboard.set(KeyboardModifierState::RCTRL, pressed),
KeyCode::AltLeft => self.keyboard.set(KeyboardModifierState::LALT, pressed), KeyCode::AltLeft => self.keyboard.set(KeyboardModifierState::LALT, pressed),
KeyCode::AltRight => self.keyboard.set(KeyboardModifierState::RALT, pressed), KeyCode::AltRight => self.keyboard.set(KeyboardModifierState::RALT, pressed),
KeyCode::MetaLeft => self.keyboard.set(KeyboardModifierState::LMETA, pressed), KeyCode::SuperLeft => self.keyboard.set(KeyboardModifierState::LSUPER, pressed),
KeyCode::MetaRight => self.keyboard.set(KeyboardModifierState::RMETA, pressed), KeyCode::SuperRight => self.keyboard.set(KeyboardModifierState::RSUPER, pressed),
_ => (), _ => (),
} }
} }
@@ -259,20 +262,19 @@ impl EventState {
pressed_mods.set(ModifiersKeys::LALT, self.keyboard.contains(KeyboardModifierState::LALT)); pressed_mods.set(ModifiersKeys::LALT, self.keyboard.contains(KeyboardModifierState::LALT));
pressed_mods.set(ModifiersKeys::RALT, self.keyboard.contains(KeyboardModifierState::RALT)); pressed_mods.set(ModifiersKeys::RALT, self.keyboard.contains(KeyboardModifierState::RALT));
if self.keyboard.intersects(KeyboardModifierState::LMETA | KeyboardModifierState::RMETA) { if self.keyboard.intersects(KeyboardModifierState::LSUPER | KeyboardModifierState::RSUPER) {
state |= ModifiersState::META state |= ModifiersState::SUPER
} }
pressed_mods pressed_mods
.set(ModifiersKeys::LMETA, self.keyboard.contains(KeyboardModifierState::LMETA)); .set(ModifiersKeys::LSUPER, self.keyboard.contains(KeyboardModifierState::LSUPER));
pressed_mods pressed_mods
.set(ModifiersKeys::RMETA, self.keyboard.contains(KeyboardModifierState::RMETA)); .set(ModifiersKeys::RSUPER, self.keyboard.contains(KeyboardModifierState::RSUPER));
Modifiers { state, pressed_mods } Modifiers { state, pressed_mods }
} }
} }
#[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
windows: Vec<(Arc<RedoxSocket>, EventState)>, windows: Vec<(Arc<RedoxSocket>, EventState)>,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
@@ -370,8 +372,10 @@ impl EventLoop {
state: element_state(pressed), state: element_state(pressed),
repeat: false, repeat: false,
text, text,
key_without_modifiers, platform_specific: KeyEventExtra {
text_with_all_modifiers, key_without_modifiers,
text_with_all_modifiers,
},
}, },
is_synthetic: false, is_synthetic: false,
}; };
@@ -650,6 +654,8 @@ impl EventLoop {
} }
} }
app.exiting(&self.window_target);
Ok(()) Ok(())
} }
@@ -658,7 +664,6 @@ impl EventLoop {
} }
} }
#[derive(Debug)]
pub struct EventLoopProxy { pub struct EventLoopProxy {
user_events_sender: mpsc::SyncSender<()>, user_events_sender: mpsc::SyncSender<()>,
pub(super) wake_socket: TimeSocket, pub(super) wake_socket: TimeSocket,
@@ -676,7 +681,6 @@ impl EventLoopProxyProvider for EventLoopProxy {
impl Unpin for EventLoopProxy {} impl Unpin for EventLoopProxy {}
#[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
control_flow: Cell<ControlFlow>, control_flow: Cell<ControlFlow>,
exit: Cell<bool>, exit: Cell<bool>,
@@ -707,7 +711,9 @@ impl RootActiveEventLoop for ActiveEventLoop {
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
Box::new(iter::empty()) let mut v = VecDeque::with_capacity(1);
v.push_back(crate::monitor::MonitorHandle { inner: MonitorHandle });
Box::new(v.into_iter())
} }
fn system_theme(&self) -> Option<Theme> { fn system_theme(&self) -> Option<Theme> {
@@ -715,7 +721,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
} }
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> { fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
None Some(crate::monitor::MonitorHandle { inner: MonitorHandle })
} }
fn listen_device_events(&self, _allowed: DeviceEvents) {} fn listen_device_events(&self, _allowed: DeviceEvents) {}

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