Compare commits

..

1 Commits

Author SHA1 Message Date
John Nunley
9c9dde9d2e m: Use better strategy for main thread detection
We can get a list of the threads in the process, and determine which
thread came first. This thread will be the one who called the main
function. So use this instead of the current strategy if it's available.

Signed-off-by: John Nunley <dev@notgull.net>
2024-11-23 09:20:29 -08:00
29 changed files with 868 additions and 565 deletions

View File

@@ -2,3 +2,4 @@
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented

View File

@@ -81,7 +81,7 @@ cursor-icon = "1.1.0"
dpi = { version = "0.1.1", path = "dpi" }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { workspace = true, optional = true }
smol_str = "0.3"
smol_str = "0.2.0"
tracing = { version = "0.1.40", default-features = false }
[dev-dependencies]
@@ -212,6 +212,7 @@ windows-sys = { version = "0.52.0", features = [
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",

View File

@@ -47,3 +47,201 @@ through the implementation work necessary to function on all platforms. When one
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently
exposed through the core, cross-platform API.
# Features
## Extending this section
If your PR makes notable changes to Winit's features, please update this section as follows:
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
## Core
### Windowing
- **Window initialization**: Winit allows the creation of a window
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
be deactivated
- **Window decorations toggle**: Decorations can be turned on or off after window creation
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
when they are. The application can precisely control its window size if desired.
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
size to specific values.
- **Window transparency**: Winit allows the creation of windows with a transparent background.
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
get drawn above their owner.
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS
* Window activation policy
* Window movable by background
* Transparent titlebar
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
* X11 Window Class
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the preferred video mode
### Web
* Get if the systems preferred color scheme is "dark"
## Compatibility Matrix
Legend:
- ✔️: Works as intended
- ▢: Mostly works, but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |✔️ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
[#242]: https://github.com/rust-windowing/winit/issues/242
[#306]: https://github.com/rust-windowing/winit/issues/306
[#315]: https://github.com/rust-windowing/winit/issues/315
[#319]: https://github.com/rust-windowing/winit/issues/319
[#33]: https://github.com/rust-windowing/winit/issues/33
[#459]: https://github.com/rust-windowing/winit/issues/459
[#5]: https://github.com/rust-windowing/winit/issues/5
[#63]: https://github.com/rust-windowing/winit/issues/63
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804

View File

@@ -2,7 +2,7 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![UNSTABLE docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=UNSTABLE%20docs
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
@@ -66,4 +66,4 @@ same MSRV policy.
### 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://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage.

View File

@@ -75,7 +75,6 @@ changelog entry.
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::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.
### Changed
@@ -161,10 +160,6 @@ changelog entry.
- In the same spirit rename `DeviceEvent::MouseMotion` to `PointerMotion`.
- Remove `Force::Calibrated::altitude_angle`.
- On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
- On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation.
- On macOS, no longer emit `Focused` upon window creation.
- On iOS, emit more events immediately, instead of queuing them.
- Update `smol_str` to version `0.3`
### Removed
@@ -211,8 +206,3 @@ changelog entry.
- 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 macOS, fixed the scancode conversion for audio volume keys.
- On macOS, fixed the scancode conversion for `IntlBackslash`.
- 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

@@ -153,9 +153,6 @@ pub enum WindowEvent {
/// Contains the new dimensions of the surface (can also be retrieved with
/// [`Window::surface_size`]).
///
/// This event will not necessarily be emitted upon window creation, query
/// [`Window::surface_size`] if you need to determine the surface's initial size.
///
/// [`Window::surface_size`]: crate::window::Window::surface_size
SurfaceResized(PhysicalSize<u32>),
@@ -175,23 +172,18 @@ pub enum WindowEvent {
/// The window has been destroyed.
Destroyed,
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
DroppedFile(PathBuf),
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted for each file
/// separately.
HoveredFile(PathBuf),
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
///
/// The support for this is known to be incomplete, see [#720] for more
/// information.
///
/// [#720]: https://github.com/rust-windowing/winit/issues/720
DroppedFile(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
@@ -201,9 +193,6 @@ pub enum WindowEvent {
/// The window gained or lost focus.
///
/// The parameter is true if the window has gained focus, and false if it has lost focus.
///
/// Windows are unfocused upon creation, but will usually be focused by the system soon
/// afterwards.
Focused(bool),
/// An event from the keyboard has been received.
@@ -212,7 +201,6 @@ pub enum WindowEvent {
/// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down,
/// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key
/// events which are not marked as `is_synthetic`.
/// - **iOS:** Unsupported.
KeyboardInput {
device_id: Option<DeviceId>,
event: KeyEvent,
@@ -416,18 +404,10 @@ pub enum WindowEvent {
/// Touchpad pressure event.
///
/// ## Platform-specific
///
/// - **macOS**: Only supported on Apple forcetouch-capable macbooks.
/// - **Android / iOS / Wayland / X11 / Windows / Orbital / Web:** Unsupported.
TouchpadPressure {
device_id: Option<DeviceId>,
/// Value between 0 and 1 representing how hard the touchpad is being
/// pressed.
pressure: f32,
/// Represents the click level.
stage: i64,
},
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the
/// touchpad is being pressed) and stage (integer representing the click level).
TouchpadPressure { device_id: Option<DeviceId>, pressure: f32, stage: i64 },
/// The window's scale factor has changed.
///
@@ -440,12 +420,7 @@ pub enum WindowEvent {
/// To update the window size, use the provided [`SurfaceSizeWriter`] handle. By default, the
/// window is resized to the value suggested by the OS, but it can be changed to any value.
///
/// This event will not necessarily be emitted upon window creation, query
/// [`Window::scale_factor`] if you need to determine the window's initial scale factor.
///
/// For more information about DPI in general, see the [`dpi`] crate.
///
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
ScaleFactorChanged {
scale_factor: f64,
/// Handle to update surface size during scale changes.

View File

@@ -1,59 +1,5 @@
//! 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
//!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with

View File

@@ -1,12 +1,18 @@
//! 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::num::{NonZeroU16, NonZeroU32};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl;
/// A handle to a fullscreen video mode of a specific monitor.
/// Describes a fullscreen video mode of a monitor.
///
/// This can be acquired with [`MonitorHandle::video_modes`].
/// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
@@ -86,15 +92,7 @@ impl std::fmt::Display for VideoModeHandle {
/// Handle to a monitor.
///
/// Allows you to retrieve basic information and metadata about a monitor.
///
/// Can be used in [`Window`] creation to place the window on a specific
/// monitor.
///
/// This can be retrieved from one of the following methods, which return an
/// iterator of [`MonitorHandle`]s:
/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors).
/// - [`Window::available_monitors`](crate::window::Window::available_monitors).
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
///
/// ## Platform-specific
///

View File

@@ -92,12 +92,17 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
/// Create `KeyEvent` for the given `NSEvent`.
///
/// This function shouldn't be called when the IME input is in process.
pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
pub(crate) fn create_key_event(
ns_event: &NSEvent,
is_press: bool,
is_repeat: bool,
key_override: Option<PhysicalKey>,
) -> KeyEvent {
use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released };
let scancode = unsafe { ns_event.keyCode() };
let mut physical_key = scancode_to_physicalkey(scancode as u32);
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32));
// NOTE: The logical key should heed both SHIFT and ALT if possible.
// For instance:
@@ -106,15 +111,20 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
// * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best.
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() {
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
let characters =
unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
if characters.is_empty() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
}
Some(SmolStr::new(characters))
};
let key_from_code = code_to_key(physical_key, scancode);
@@ -367,7 +377,6 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::KeyX => Some(0x07),
KeyCode::KeyC => Some(0x08),
KeyCode::KeyV => Some(0x09),
KeyCode::IntlBackslash => Some(0x0a),
KeyCode::KeyB => Some(0x0b),
KeyCode::KeyQ => Some(0x0c),
KeyCode::KeyW => Some(0x0d),
@@ -413,21 +422,18 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::SuperRight => Some(0x36),
KeyCode::SuperLeft => Some(0x37),
KeyCode::ShiftLeft => Some(0x38),
KeyCode::CapsLock => Some(0x39),
KeyCode::AltLeft => Some(0x3a),
KeyCode::ControlLeft => Some(0x3b),
KeyCode::ShiftRight => Some(0x3c),
KeyCode::AltRight => Some(0x3d),
KeyCode::ControlRight => Some(0x3e),
KeyCode::Fn => Some(0x3f),
KeyCode::F17 => Some(0x40),
KeyCode::NumpadDecimal => Some(0x41),
KeyCode::NumpadMultiply => Some(0x43),
KeyCode::NumpadAdd => Some(0x45),
KeyCode::NumLock => Some(0x47),
KeyCode::AudioVolumeUp => Some(0x48),
KeyCode::AudioVolumeDown => Some(0x49),
KeyCode::AudioVolumeMute => Some(0x4a),
KeyCode::AudioVolumeUp => Some(0x49),
KeyCode::AudioVolumeDown => Some(0x4a),
KeyCode::NumpadDivide => Some(0x4b),
KeyCode::NumpadEnter => Some(0x4c),
KeyCode::NumpadSubtract => Some(0x4e),
@@ -446,22 +452,17 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::Numpad8 => Some(0x5b),
KeyCode::Numpad9 => Some(0x5c),
KeyCode::IntlYen => Some(0x5d),
KeyCode::IntlRo => Some(0x5e),
KeyCode::NumpadComma => Some(0x5f),
KeyCode::F5 => Some(0x60),
KeyCode::F6 => Some(0x61),
KeyCode::F7 => Some(0x62),
KeyCode::F3 => Some(0x63),
KeyCode::F8 => Some(0x64),
KeyCode::F9 => Some(0x65),
KeyCode::Lang2 => Some(0x66),
KeyCode::F11 => Some(0x67),
KeyCode::Lang1 => Some(0x68),
KeyCode::F13 => Some(0x69),
KeyCode::F16 => Some(0x6a),
KeyCode::F14 => Some(0x6b),
KeyCode::F10 => Some(0x6d),
KeyCode::ContextMenu => Some(0x6e),
KeyCode::F12 => Some(0x6f),
KeyCode::F15 => Some(0x71),
KeyCode::Insert => Some(0x72),
@@ -477,26 +478,11 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::ArrowRight => Some(0x7c),
KeyCode::ArrowDown => Some(0x7d),
KeyCode::ArrowUp => Some(0x7e),
KeyCode::Power => Some(0x7f),
_ => None,
}
}
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
//
// See also:
// Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
//
// Also see https://developer.apple.com/documentation/appkit/function-key-unicode-values:
//
// > the system handles some function keys at a lower level and your app never sees them.
// > Examples include the Volume Up key, Volume Down key, Volume Mute key, Eject key, and
// > Function key found on many Macs.
//
// So the handling of some of these is mostly for show.
PhysicalKey::Code(match scancode {
0x00 => KeyCode::KeyA,
0x01 => KeyCode::KeyS,
@@ -508,11 +494,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x07 => KeyCode::KeyX,
0x08 => KeyCode::KeyC,
0x09 => KeyCode::KeyV,
// This key is typically located near LeftShift key, roughly the same location as backquote
// (`) on Windows' US layout.
//
// The keycap varies on international keyboards.
0x0a => KeyCode::IntlBackslash,
// 0x0a => World 1,
0x0b => KeyCode::KeyB,
0x0c => KeyCode::KeyQ,
0x0d => KeyCode::KeyW,
@@ -554,7 +536,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x31 => KeyCode::Space,
0x32 => KeyCode::Backquote,
0x33 => KeyCode::Backspace,
// 0x34 => unknown, // kVK_Powerbook_KeypadEnter
// 0x34 => unknown,
0x35 => KeyCode::Escape,
0x36 => KeyCode::SuperRight,
0x37 => KeyCode::SuperLeft,
@@ -573,10 +555,15 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 0x44 => unknown,
0x45 => KeyCode::NumpadAdd,
// 0x46 => unknown,
0x47 => KeyCode::NumLock, // kVK_ANSI_KeypadClear
0x48 => KeyCode::AudioVolumeUp,
0x49 => KeyCode::AudioVolumeDown,
0x4a => KeyCode::AudioVolumeMute,
0x47 => KeyCode::NumLock,
// 0x48 => KeyCode::NumpadClear,
// TODO: (Artur) for me, kVK_VolumeUp is 0x48
// macOS 10.11
// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/
// Versions/A/Headers/Events.h
0x49 => KeyCode::AudioVolumeUp,
0x4a => KeyCode::AudioVolumeDown,
0x4b => KeyCode::NumpadDivide,
0x4c => KeyCode::NumpadEnter,
// 0x4d => unknown,
@@ -596,23 +583,23 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x5b => KeyCode::Numpad8,
0x5c => KeyCode::Numpad9,
0x5d => KeyCode::IntlYen,
0x5e => KeyCode::IntlRo,
0x5f => KeyCode::NumpadComma,
// 0x5e => JIS Ro,
// 0x5f => unknown,
0x60 => KeyCode::F5,
0x61 => KeyCode::F6,
0x62 => KeyCode::F7,
0x63 => KeyCode::F3,
0x64 => KeyCode::F8,
0x65 => KeyCode::F9,
0x66 => KeyCode::Lang2,
// 0x66 => JIS Eisuu (macOS),
0x67 => KeyCode::F11,
0x68 => KeyCode::Lang1,
// 0x68 => JIS Kanna (macOS),
0x69 => KeyCode::F13,
0x6a => KeyCode::F16,
0x6b => KeyCode::F14,
// 0x6c => unknown,
0x6d => KeyCode::F10,
0x6e => KeyCode::ContextMenu,
// 0x6e => unknown,
0x6f => KeyCode::F12,
// 0x70 => unknown,
0x71 => KeyCode::F15,
@@ -629,7 +616,11 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x7c => KeyCode::ArrowRight,
0x7d => KeyCode::ArrowDown,
0x7e => KeyCode::ArrowUp,
0x7f => KeyCode::Power, // On 10.7 and 10.8 only
// 0x7f => unknown,
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
// backquote (`) on Windows' US layout.
0xa => KeyCode::Backquote,
_ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
})
}

View File

@@ -4,30 +4,30 @@ use std::collections::{HashMap, VecDeque};
use std::ptr;
use std::rc::Rc;
use objc2::rc::Retained;
use objc2::rc::{Retained, WeakId};
use objc2::runtime::{AnyObject, Sel};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView,
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
};
use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
NSSize, NSString, NSUInteger,
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol,
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use super::app_state::AppState;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey, KeyEventExtra,
scancode_to_physicalkey,
};
use super::window::WinitWindow;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, PointerKind,
PointerSource, TouchPhase, WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
@@ -134,6 +134,9 @@ pub struct ViewState {
marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool,
// Weak reference because the window keeps a strong reference to the view
_ns_window: WeakId<WinitWindow>,
/// The state of the `Option` as `Alt`.
option_as_alt: Cell<OptionAsAlt>,
}
@@ -174,10 +177,9 @@ declare_class!(
self.ivars().tracking_rect.set(Some(tracking_rect));
}
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[method(viewFrameDidChangeNotification:)]
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification");
#[method(frameDidChange:)]
fn frame_did_change(&self, _event: &NSEvent) {
trace_scope!("frameDidChange:");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
@@ -201,7 +203,10 @@ declare_class!(
fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:");
self.ivars().app_state.handle_redraw(self.window().id());
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
if let Some(window) = self.ivars()._ns_window.load() {
self.ivars().app_state.handle_redraw(window.id());
}
// This is a direct subclass of NSView, no need to call superclass' drawRect:
}
@@ -394,7 +399,7 @@ declare_class!(
unsafe { &*string }.to_string()
};
let is_control = string.chars().next().is_some_and(|c| c.is_control());
let is_control = string.chars().next().map_or(false, |c| c.is_control());
// Commit only if we have marked text.
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
@@ -490,7 +495,7 @@ declare_class!(
};
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: None,
event: key_event,
@@ -513,7 +518,7 @@ declare_class!(
) {
self.queue_event(WindowEvent::KeyboardInput {
device_id: None,
event: create_key_event(&event, false, false),
event: create_key_event(&event, false, false, None),
is_synthetic: false,
});
}
@@ -560,7 +565,7 @@ declare_class!(
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() });
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: None,
@@ -801,10 +806,11 @@ declare_class!(
impl WinitView {
pub(super) fn new(
app_state: &Rc<AppState>,
window: &WinitWindow,
accepts_first_mouse: bool,
option_as_alt: OptionAsAlt,
mtm: MainThreadMarker,
) -> Retained<Self> {
let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState {
app_state: Rc::clone(app_state),
cursor_state: Default::default(),
@@ -819,24 +825,34 @@ impl WinitView {
forward_key_to_app: Default::default(),
marked_text: Default::default(),
accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.setPostsFrameChangedNotifications(true);
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&this,
sel!(frameDidChange:),
Some(NSViewFrameDidChangeNotification),
Some(&this),
)
}
*this.ivars().input_source.borrow_mut() = this.current_input_source();
this
}
fn window(&self) -> Retained<WinitWindow> {
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) }
// TODO: Simply use `window` property on `NSView`.
// That only returns a window _after_ the view has been attached though!
// (which is incompatible with `frameDidChange:`)
//
// unsafe { msg_send_id![self, window] }
self.ivars()._ns_window.load().expect("view to have a window")
}
fn queue_event(&self, event: WindowEvent) {
@@ -946,36 +962,22 @@ impl WinitView {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = scancode_to_physicalkey(scancode as u32);
let logical_key = code_to_key(physical_key, scancode);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
// Ignore processing of unknown modifiers because we can't determine whether
// it was pressed or release reliably.
//
// Furthermore, sometimes normal keys are reported inside flagsChanged:, such as
// when holding Caps Lock while pressing another key, see:
// https://github.com/alacritty/alacritty/issues/8268
let Some(event_modifier) = key_to_modifier(&logical_key) else {
let Some(event_modifier) = key_to_modifier(&key) else {
break 'send_event;
};
let mut event = KeyEvent {
location: code_to_location(physical_key),
logical_key: logical_key.clone(),
physical_key,
repeat: false,
// We'll correct this later.
state: Pressed,
text: None,
platform_specific: KeyEventExtra {
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod =
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);

View File

@@ -15,15 +15,15 @@ use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSWindowButton, NSWindowDelegate,
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
NSWindowToolbarStyle,
};
use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming,
NSKeyValueObservingOptions, NSObject, NSObjectNSDelayedPerforming,
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
@@ -170,7 +170,7 @@ declare_class!(
#[method(windowDidResize:)]
fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:");
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
// NOTE: WindowEvent::SurfaceResized is reported in frameDidChange.
self.emit_move_event();
}
@@ -658,9 +658,9 @@ fn new_window(
let view = WinitView::new(
app_state,
&window,
attrs.platform_specific.accepts_first_mouse,
attrs.platform_specific.option_as_alt,
mtm,
);
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
@@ -682,23 +682,6 @@ fn new_window(
window.setContentView(Some(&view));
window.setInitialFirstResponder(Some(&view));
// Configure the view to send notifications whenever its frame rectangle changes.
//
// We explicitly do this _after_ setting the view as the content view of the window, to
// avoid a resize event when creating the window.
view.setPostsFrameChangedNotifications(true);
// `setPostsFrameChangedNotifications` posts the notification immediately, so register the
// observer _after_, again so that the event isn't triggered initially.
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&view,
sel!(viewFrameDidChangeNotification:),
Some(NSViewFrameDidChangeNotification),
Some(&view),
)
}
if attrs.transparent {
window.setOpaque(false);
// See `set_transparent` for details on why we do this.
@@ -782,6 +765,12 @@ impl WindowDelegate {
});
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
if scale_factor != 1.0 {
let delegate = delegate.clone();
RunLoop::main(mtm).queue_closure(move || {
delegate.handle_scale_factor_changed(scale_factor);
});
}
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
// Listen for theme change event.
@@ -812,6 +801,10 @@ impl WindowDelegate {
delegate.set_cursor(attrs.cursor);
// XXX Send `Focused(false)` right after creating the window delegate, so we won't
// obscure the real focused events on the startup.
delegate.queue_event(WindowEvent::Focused(false));
// Set fullscreen mode after we setup everything
delegate.set_fullscreen(attrs.fullscreen.map(Into::into));
@@ -962,21 +955,8 @@ impl WindowDelegate {
}
pub fn surface_position(&self) -> PhysicalPosition<i32> {
// The calculation here is a bit awkward because we've gotta reconcile the
// different origins (Winit prefers top-left vs. NSWindow's bottom-left),
// and I couldn't find a built-in way to do so.
// The position of the window and the view, both in Winit screen coordinates.
let window_position = flip_window_screen_coordinates(self.window().frame());
let view_position = flip_window_screen_coordinates(
self.window().contentRectForFrameRect(self.window().frame()),
);
// And use that to convert the view position to window coordinates.
let surface_position =
NSPoint::new(view_position.x - window_position.x, view_position.y - window_position.y);
let logical = LogicalPosition::new(surface_position.x, surface_position.y);
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let logical = LogicalPosition::new(content_rect.origin.x, content_rect.origin.y);
logical.to_physical(self.scale_factor())
}
@@ -1010,24 +990,19 @@ impl WindowDelegate {
// we've set it up with `additionalSafeAreaInsets`.
unsafe { self.view().safeAreaInsets() }
} else {
// If `safeAreaInsets` is not available, we'll have to do the calculation ourselves.
let window_rect = unsafe {
self.window().convertRectFromScreen(
self.window().contentRectForFrameRect(self.window().frame()),
)
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
// Includes NSWindowStyleMask::FullSizeContentView
// Convert from window coordinates to view coordinates
let safe_rect = unsafe {
self.view().convertRect_fromView(self.window().contentLayoutRect(), None)
};
// This includes NSWindowStyleMask::FullSizeContentView.
let layout_rect = unsafe { self.window().contentLayoutRect() };
// Calculate the insets from window coordinates in AppKit's coordinate system.
NSEdgeInsets {
top: (window_rect.size.height + window_rect.origin.y)
- (layout_rect.size.height + layout_rect.origin.y),
left: layout_rect.origin.x - window_rect.origin.x,
bottom: layout_rect.origin.y - window_rect.origin.y,
right: (window_rect.size.width + window_rect.origin.x)
- (layout_rect.size.width + layout_rect.origin.x),
top: safe_rect.origin.y - content_rect.origin.y,
left: safe_rect.origin.x - content_rect.origin.x,
bottom: (content_rect.size.height + content_rect.origin.x)
- (safe_rect.size.height + safe_rect.origin.x),
right: (content_rect.size.width + content_rect.origin.y)
- (safe_rect.size.width + safe_rect.origin.y),
}
};
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);

View File

@@ -106,6 +106,7 @@ impl EventHandler {
self.inner.try_borrow().is_err()
}
#[cfg(target_os = "macos")]
pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}

View File

@@ -27,9 +27,8 @@ use super::window::WinitUIWindow;
use super::{ActiveEventLoop, EventLoopProxy};
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
macro_rules! bug {
($($msg:tt)*) => {
@@ -68,9 +67,25 @@ fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
GLOBAL.get(mtm).get_or_init(EventHandler::new)
}
fn handle_event(mtm: MainThreadMarker, event: Event) {
let event_loop = &ActiveEventLoop { mtm };
get_handler(mtm).handle(|app| match event {
Event::NewEvents(cause) => app.new_events(event_loop, cause),
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
Event::UserWakeUp => app.proxy_wake_up(event_loop),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::CreateSurfaces => app.can_create_surfaces(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
})
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
Window { window_id: WindowId, event: WindowEvent },
StaticEvent(Event),
ScaleFactorChanged(ScaleFactorChanged),
}
@@ -81,9 +96,14 @@ pub struct ScaleFactorChanged {
pub(super) scale_factor: f64,
}
impl EventWrapper {
enum UserCallbackTransitionResult<'a> {
Success { active_control_flow: ControlFlow, processing_redraws: bool },
ReentrancyPrevented { queued_events: &'a mut Vec<EventWrapper> },
}
impl Event {
fn is_redraw(&self) -> bool {
matches!(self, Self::Window { event: WindowEvent::RedrawRequested, .. })
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
}
}
@@ -92,12 +112,18 @@ impl EventWrapper {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
Initial {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingEvents {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingRedraws {
active_control_flow: ControlFlow,
},
@@ -114,7 +140,6 @@ pub(crate) struct AppState {
control_flow: ControlFlow,
waker: EventLoopWaker,
event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Vec<EventWrapper>,
}
impl AppState {
@@ -133,11 +158,13 @@ impl AppState {
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
app_state: Some(AppStateImpl::Initial {
queued_events: Vec::new(),
queued_gpu_redraws: HashSet::new(),
}),
control_flow: ControlFlow::default(),
waker,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
queued_events: Vec::new(),
});
}
init_guard(&mut guard);
@@ -190,34 +217,48 @@ impl AppState {
matches!(self.state(), AppStateImpl::Terminated)
}
fn did_finish_launching_transition(&mut self) {
let queued_gpu_redraws = match self.take_state() {
AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws,
fn did_finish_launching_transition(&mut self) -> Vec<EventWrapper> {
let (events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Initial { queued_events, queued_gpu_redraws } => {
(queued_events, queued_gpu_redraws)
},
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
events
}
fn wakeup_transition(&mut self) -> Option<StartCause> {
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() || self.has_terminated() {
return None;
}
let start_cause = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll,
let event = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll))
},
(ControlFlow::Wait, AppStateImpl::Waiting { start }) => {
StartCause::WaitCancelled { start, requested_resume: None }
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
}))
},
(ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { start }) => {
if Instant::now() >= requested_resume {
StartCause::ResumeTimeReached { start, requested_resume }
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
}))
} else {
StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) }
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
}))
}
},
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
@@ -227,7 +268,55 @@ impl AppState {
queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow,
});
Some(start_cause)
Some(event)
}
fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
// If we're not able to process an event due to recursion or `Init` not having been sent out
// yet, then queue the events up.
match self.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_events, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => {
// A lifetime cast: early returns are not currently handled well with NLL, but
// polonius handles them well. This transmute is a safe workaround.
return unsafe {
mem::transmute::<
UserCallbackTransitionResult<'_>,
UserCallbackTransitionResult<'_>,
>(UserCallbackTransitionResult::ReentrancyPrevented {
queued_events,
})
};
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::PollFinished { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::Terminated => {
bug!("unexpected attempted to process an event {:?}", s)
},
}
let (queued_gpu_redraws, active_control_flow, processing_redraws) = match self.take_state()
{
AppStateImpl::Initial { .. } | AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => {
(queued_gpu_redraws, active_control_flow, false)
},
AppStateImpl::ProcessingRedraws { active_control_flow } => {
(Default::default(), active_control_flow, true)
},
AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(),
};
self.set_state(AppStateImpl::InUserCallback {
queued_events: Vec::new(),
queued_gpu_redraws,
});
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws }
}
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
@@ -283,7 +372,7 @@ impl AppState {
fn terminated_transition(&mut self) {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { .. } => {},
s => bug!("terminated while not processing events {:?}", s),
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
}
}
@@ -304,7 +393,8 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<W
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => {
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. } => {
let _ = queued_gpu_redraws.insert(window);
},
s @ &mut AppStateImpl::ProcessingRedraws { .. }
@@ -328,24 +418,27 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
AppState::get_mut(mtm).did_finish_launching_transition();
let events = AppState::get_mut(mtm).did_finish_launching_transition();
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
let events = [
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)),
EventWrapper::StaticEvent(Event::CreateSurfaces),
]
.into_iter()
.chain(events);
handle_nonuser_events(mtm, events);
}
// AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let cause = match this.wakeup_transition() {
let wakeup_event = match this.wakeup_transition() {
None => return,
Some(cause) => cause,
Some(wakeup_event) => wakeup_event,
};
drop(this);
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause));
handle_nonuser_events(mtm, []);
handle_nonuser_event(mtm, wakeup_event)
}
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
@@ -361,75 +454,132 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
return;
}
if !get_handler(mtm).ready() {
// Prevent re-entrancy; queue the events up for once we're done handling the event instead.
this.queued_events.extend(events);
return;
}
let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. });
let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events);
return;
},
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => {
(active_control_flow, processing_redraws)
},
};
drop(this);
for event in events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
for wrapper in events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_event(mtm, event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
handle_wrapped_event(mtm, event)
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = mem::take(&mut this.queued_events);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(if processing_redraws {
bug_assert!(
queued_gpu_redraws.is_empty(),
"redraw queued while processing redraws"
);
AppStateImpl::ProcessingRedraws { active_control_flow }
} else {
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow }
});
break;
}
drop(this);
for event in queued_events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: \
{:#?}",
event
);
}
handle_event(mtm, event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
handle_wrapped_event(mtm, event);
}
}
}
fn handle_user_events(mtm: MainThreadMarker) {
let this = AppState::get_mut(mtm);
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
let mut this = AppState::get_mut(mtm);
let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event")
},
UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => {
(active_control_flow, processing_redraws)
},
};
if processing_redraws {
bug!("user events attempted to be sent out while `ProcessingRedraws`");
}
let event_loop_proxy = this.event_loop_proxy().clone();
drop(this);
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
handle_event(mtm, Event::UserWakeUp);
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = mem::take(&mut this.queued_events);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state =
Some(AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow });
break;
}
drop(this);
for event in queued_events {
handle_wrapped_event(mtm, event);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => handle_event(mtm, event),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
}
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
handle_event(mtm, Event::UserWakeUp);
}
}
}
@@ -447,10 +597,10 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::Window {
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::Occluded(occluded),
});
}));
}
}
handle_nonuser_events(mtm, events);
@@ -473,37 +623,23 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
.map(|window| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
})
})
.collect();
drop(this);
handle_nonuser_events(mtm, redraw_events);
get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
}
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub(crate) fn handle_resumed(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.resumed(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_suspended(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.suspended(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_memory_warning(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.memory_warning(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
@@ -517,10 +653,10 @@ pub(crate) fn terminated(application: &UIApplication) {
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::Window {
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::Destroyed,
});
}));
}
}
handle_nonuser_events(mtm, events);
@@ -529,26 +665,20 @@ pub(crate) fn terminated(application: &UIApplication) {
this.terminated_transition();
drop(this);
get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
}
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
match event {
EventWrapper::Window { window_id, event } => get_handler(mtm)
.handle(|app| app.window_event(&ActiveEventLoop { mtm }, window_id, event)),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
handle_event(mtm, Event::LoopExiting)
}
fn handle_hidpi_proxy(mtm: MainThreadMarker, event: ScaleFactorChanged) {
let ScaleFactorChanged { suggested_size, scale_factor, window } = event;
let new_surface_size = Arc::new(Mutex::new(suggested_size));
get_handler(mtm).handle(|app| {
app.window_event(&ActiveEventLoop { mtm }, window.id(), WindowEvent::ScaleFactorChanged {
let event = Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
});
});
},
};
handle_event(mtm, event);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_size);

View File

@@ -23,10 +23,11 @@ use objc2_ui_kit::{
use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer;
use super::app_state::{send_occluded_event_for_all_windows, AppState};
use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper};
use super::{app_state, monitor, MonitorHandle};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::Event;
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
@@ -173,13 +174,17 @@ impl EventLoop {
&center,
// `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| app_state::handle_resumed(mtm),
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
},
);
let _will_resign_active_observer = create_observer(
&center,
// `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification },
move |_| app_state::handle_suspended(mtm),
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
},
);
let _will_enter_foreground_observer = create_observer(
&center,
@@ -226,7 +231,12 @@ impl EventLoop {
&center,
// `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| app_state::handle_memory_warning(mtm),
move |_| {
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::MemoryWarning),
);
},
);
Ok(EventLoop {

View File

@@ -101,20 +101,13 @@ impl Clone for MonitorHandle {
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
(self as *const Self).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
ptr::eq(
Retained::as_ptr(self.ui_screen.get(mtm)),
Retained::as_ptr(other.ui_screen.get(mtm)),
)
ptr::eq(self, other)
}
}
@@ -128,10 +121,8 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: Only getting the pointer.
// TODO: Make a better ordering
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
(self as *const Self).cmp(&(other as *const Self))
}
}
@@ -249,27 +240,3 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
#[allow(deprecated)]
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
}
#[cfg(test)]
mod tests {
use objc2_foundation::NSSet;
use super::*;
// Test that UIScreen pointer comparisons are correct.
#[test]
#[allow(deprecated)]
fn screen_comparisons() {
// Test code, doesn't matter that it's not thread safe
let mtm = unsafe { MainThreadMarker::new_unchecked() };
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
});
}
}

View File

@@ -17,8 +17,8 @@ use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalPosition;
use crate::event::{
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase,
WindowEvent,
ButtonSource, ElementState, Event, FingerId, Force, KeyEvent, PointerKind, PointerSource,
TouchPhase, WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::platform_impl::KeyEventExtra;
@@ -60,10 +60,10 @@ declare_class!(
let window = self.window().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::Window {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
},
}),
);
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
@@ -84,10 +84,10 @@ declare_class!(
let window = self.window().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::Window {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::SurfaceResized(size),
},
}),
);
}
@@ -131,11 +131,12 @@ declare_class!(
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::Window {
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
},
)),
))),
);
}
@@ -191,14 +192,14 @@ declare_class!(
state => panic!("unexpected recognizer state: {state:?}"),
};
let gesture_event = EventWrapper::Window {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::PinchGesture {
device_id: None,
delta: delta as f64,
phase,
},
};
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
@@ -209,12 +210,12 @@ declare_class!(
let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::Window {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::DoubleTapGesture {
device_id: None,
},
};
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
@@ -251,14 +252,14 @@ declare_class!(
};
// Make delta negative to match macos, convert to degrees
let gesture_event = EventWrapper::Window {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::RotationGesture {
device_id: None,
delta: -delta.to_degrees() as _,
phase,
},
};
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
@@ -302,14 +303,14 @@ declare_class!(
};
let gesture_event = EventWrapper::Window {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::PanGesture {
device_id: None,
delta: PhysicalPosition::new(dx as _, dy as _),
phase,
},
};
});
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
@@ -537,7 +538,7 @@ impl WinitView {
}
};
touch_events.push(EventWrapper::Window {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::PointerEntered {
device_id: None,
@@ -549,8 +550,8 @@ impl WinitView {
PointerKind::Touch(finger_id)
},
},
});
touch_events.push(EventWrapper::Window {
}));
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::PointerButton {
device_id: None,
@@ -563,7 +564,7 @@ impl WinitView {
ButtonSource::Touch { finger_id, force }
},
},
});
}));
},
UITouchPhase::Moved => {
let (primary, source) = if let UITouchType::Pencil = touch_type {
@@ -575,7 +576,7 @@ impl WinitView {
})
};
touch_events.push(EventWrapper::Window {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::PointerMoved {
device_id: None,
@@ -583,7 +584,7 @@ impl WinitView {
position,
source,
},
});
}));
},
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended | UITouchPhase::Cancelled => {
@@ -599,7 +600,7 @@ impl WinitView {
};
if let UITouchPhase::Ended = phase {
touch_events.push(EventWrapper::Window {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::PointerButton {
device_id: None,
@@ -612,10 +613,10 @@ impl WinitView {
ButtonSource::Touch { finger_id, force }
},
},
});
}));
}
touch_events.push(EventWrapper::Window {
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::PointerLeft {
device_id: None,
@@ -627,7 +628,7 @@ impl WinitView {
PointerKind::Touch(finger_id)
},
},
});
}));
},
_ => panic!("unexpected touch phase: {phase:?}"),
}
@@ -646,25 +647,29 @@ impl WinitView {
text.to_string().chars().flat_map(|c| {
let text = smol_str::SmolStr::from_iter([c]);
// Emit both press and release events
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
text: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
event: KeyEvent {
text: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(
NativeKeyCode::Unidentified,
),
platform_specific: KeyEventExtra {},
},
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
platform_specific: KeyEventExtra {},
is_synthetic: false,
device_id: None,
},
is_synthetic: false,
},
})
})
}),
);
@@ -676,21 +681,23 @@ impl WinitView {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(
mtm,
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
state,
logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace),
platform_specific: KeyEventExtra {},
repeat: false,
location: KeyLocation::Standard,
text: None,
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
state,
logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace),
platform_specific: KeyEventExtra {},
repeat: false,
location: KeyLocation::Standard,
text: None,
},
is_synthetic: false,
},
is_synthetic: false,
},
})
}),
);
}

View File

@@ -23,7 +23,7 @@ use crate::dpi::{
Position, Size,
};
use crate::error::{NotSupportedError, RequestError};
use crate::event::WindowEvent;
use crate::event::{Event, WindowEvent};
use crate::icon::Icon;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
@@ -51,10 +51,10 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::Window {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: self.id(),
event: WindowEvent::Focused(true),
},
}),
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
@@ -64,10 +64,10 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::Window {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: self.id(),
event: WindowEvent::Focused(false),
},
}),
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
@@ -517,6 +517,30 @@ impl Window {
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller);
window.makeKeyAndVisible();
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `SurfaceResized`
// event on window creation if the DPI factor != 1.0
let scale_factor = view.contentScaleFactor();
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let frame = view.frame();
let size =
LogicalSize { width: frame.size.width as f64, height: frame.size.height as f64 };
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
window: window.clone(),
scale_factor,
suggested_size: size.to_physical(scale_factor),
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id: window.id(),
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
},
))),
);
}
let inner = Inner { window, view_controller, view, gl_or_metal_backed };
Ok(Window { inner: MainThreadBound::new(inner, mtm) })
}

View File

@@ -35,9 +35,6 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
// I can only hope they agree on what the keycodes mean.
//
// The mapping here is heavily influenced by Firefox' source:
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
//
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are
// difficult to test the correctness of, or discover the purpose of. Because of this, they've
// either been commented out here, or not included at all.
@@ -169,23 +166,23 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
125 => KeyCode::SuperLeft,
126 => KeyCode::SuperRight,
127 => KeyCode::ContextMenu,
128 => KeyCode::BrowserStop,
129 => KeyCode::Again,
130 => KeyCode::Props,
131 => KeyCode::Undo,
132 => KeyCode::Select, // FRONT
133 => KeyCode::Copy,
134 => KeyCode::Open,
135 => KeyCode::Paste,
136 => KeyCode::Find,
137 => KeyCode::Cut,
138 => KeyCode::Help,
// 128 => KeyCode::STOP,
// 129 => KeyCode::AGAIN,
// 130 => KeyCode::PROPS,
// 131 => KeyCode::UNDO,
// 132 => KeyCode::FRONT,
// 133 => KeyCode::COPY,
// 134 => KeyCode::OPEN,
// 135 => KeyCode::PASTE,
// 136 => KeyCode::FIND,
// 137 => KeyCode::CUT,
// 138 => KeyCode::HELP,
// 139 => KeyCode::MENU,
140 => KeyCode::LaunchApp2, // CALC
// 140 => KeyCode::CALC,
// 141 => KeyCode::SETUP,
// 142 => KeyCode::SLEEP,
143 => KeyCode::WakeUp,
144 => KeyCode::LaunchApp1, // FILE
// 143 => KeyCode::WAKEUP,
// 144 => KeyCode::FILE,
// 145 => KeyCode::SENDFILE,
// 146 => KeyCode::DELETEFILE,
// 147 => KeyCode::XFER,
@@ -196,13 +193,13 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 152 => KeyCode::COFFEE,
// 153 => KeyCode::ROTATE_DISPLAY,
// 154 => KeyCode::CYCLEWINDOWS,
155 => KeyCode::LaunchMail,
156 => KeyCode::BrowserFavorites, // BOOKMARKS
// 155 => KeyCode::MAIL,
// 156 => KeyCode::BOOKMARKS,
// 157 => KeyCode::COMPUTER,
158 => KeyCode::BrowserBack,
159 => KeyCode::BrowserForward,
// 158 => KeyCode::BACK,
// 159 => KeyCode::FORWARD,
// 160 => KeyCode::CLOSECD,
161 => KeyCode::Eject, // EJECTCD
// 161 => KeyCode::EJECTCD,
// 162 => KeyCode::EJECTCLOSECD,
163 => KeyCode::MediaTrackNext,
164 => KeyCode::MediaPlayPause,
@@ -212,9 +209,9 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 168 => KeyCode::REWIND,
// 169 => KeyCode::PHONE,
// 170 => KeyCode::ISO,
171 => KeyCode::MediaSelect, // CONFIG
172 => KeyCode::BrowserHome,
173 => KeyCode::BrowserRefresh,
// 171 => KeyCode::CONFIG,
// 172 => KeyCode::HOMEPAGE,
// 173 => KeyCode::REFRESH,
// 174 => KeyCode::EXIT,
// 175 => KeyCode::MOVE,
// 176 => KeyCode::EDIT,
@@ -253,7 +250,7 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 214 => KeyCode::QUESTION,
// 215 => KeyCode::EMAIL,
// 216 => KeyCode::CHAT,
217 => KeyCode::BrowserSearch,
// 217 => KeyCode::SEARCH,
// 218 => KeyCode::CONNECT,
// 219 => KeyCode::FINANCE,
// 220 => KeyCode::SPORT,
@@ -422,32 +419,10 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::SuperLeft => Some(125),
KeyCode::SuperRight => Some(126),
KeyCode::ContextMenu => Some(127),
KeyCode::BrowserStop => Some(128),
KeyCode::Again => Some(129),
KeyCode::Props => Some(130),
KeyCode::Undo => Some(131),
KeyCode::Select => Some(132),
KeyCode::Copy => Some(133),
KeyCode::Open => Some(134),
KeyCode::Paste => Some(135),
KeyCode::Find => Some(136),
KeyCode::Cut => Some(137),
KeyCode::Help => Some(138),
KeyCode::LaunchApp2 => Some(140),
KeyCode::WakeUp => Some(143),
KeyCode::LaunchApp1 => Some(144),
KeyCode::LaunchMail => Some(155),
KeyCode::BrowserFavorites => Some(156),
KeyCode::BrowserBack => Some(158),
KeyCode::BrowserForward => Some(159),
KeyCode::Eject => Some(161),
KeyCode::MediaTrackNext => Some(163),
KeyCode::MediaPlayPause => Some(164),
KeyCode::MediaTrackPrevious => Some(165),
KeyCode::MediaStop => Some(166),
KeyCode::MediaSelect => Some(171),
KeyCode::BrowserHome => Some(172),
KeyCode::BrowserRefresh => Some(173),
KeyCode::F13 => Some(183),
KeyCode::F14 => Some(184),
KeyCode::F15 => Some(185),
@@ -460,7 +435,6 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::F22 => Some(192),
KeyCode::F23 => Some(193),
KeyCode::F24 => Some(194),
KeyCode::BrowserSearch => Some(217),
_ => None,
}
}

View File

@@ -183,7 +183,7 @@ pub struct KeyContext<'a> {
scratch_buffer: &'a mut Vec<u8>,
}
impl KeyContext<'_> {
impl<'a> KeyContext<'a> {
pub fn process_key_event(
&mut self,
keycode: u32,

View File

@@ -27,7 +27,10 @@ use sctk::seat::pointer::{
use sctk::seat::SeatState;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent};
use crate::event::{
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, WindowId};

View File

@@ -165,7 +165,7 @@ fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
buffer: &'a mut Vec<u8>,
}
impl std::fmt::Write for Writer<'_> {
impl<'a> std::fmt::Write for Writer<'a> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.extend_from_slice(s.as_bytes());
Ok(())

View File

@@ -762,14 +762,14 @@ impl<'a> DeviceInfo<'a> {
}
}
impl Drop for DeviceInfo<'_> {
impl<'a> Drop for DeviceInfo<'a> {
fn drop(&mut self) {
assert!(!self.info.is_null());
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}
impl Deref for DeviceInfo<'_> {
impl<'a> Deref for DeviceInfo<'a> {
type Target = [ffi::XIDeviceInfo];
fn deref(&self) -> &Self::Target {
@@ -954,7 +954,7 @@ trait CookieResultExt {
fn expect_then_ignore_error(self, msg: &str);
}
impl<E: fmt::Debug> CookieResultExt for Result<VoidCookie<'_>, E> {
impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
fn expect_then_ignore_error(self, msg: &str) {
self.expect(msg).ignore_error()
}

View File

@@ -280,7 +280,7 @@ impl XConnection {
let info = self
.xcb_connection()
.extension_information(randr::X11_EXTENSION_NAME)?
.ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
.ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
// Select input data.
let event_mask =

View File

@@ -19,7 +19,7 @@ impl<'a, T> XSmartPointer<'a, T> {
}
}
impl<T> Deref for XSmartPointer<'_, T> {
impl<'a, T> Deref for XSmartPointer<'a, T> {
type Target = T;
fn deref(&self) -> &T {
@@ -27,13 +27,13 @@ impl<T> Deref for XSmartPointer<'_, T> {
}
}
impl<T> DerefMut for XSmartPointer<'_, T> {
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
}
impl<T> Drop for XSmartPointer<'_, T> {
impl<'a, T> Drop for XSmartPointer<'a, T> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFree)(self.ptr as *mut _);

View File

@@ -125,7 +125,7 @@ impl<'a> WindowProperties<'a> {
}
}
impl fmt::Display for WindowProperties<'_> {
impl<'a> fmt::Display for WindowProperties<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,

View File

@@ -20,10 +20,12 @@ use windows_sys::Win32::Graphics::Gdi::{
GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient,
ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE,
};
use windows_sys::Win32::System::Diagnostics::ToolHelp as toolhelp;
use windows_sys::Win32::System::Ole::RevokeDragDrop;
use windows_sys::Win32::System::Threading::{
CreateWaitableTimerExW, GetCurrentThreadId, SetWaitableTimer,
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, INFINITE, TIMER_ALL_ACCESS,
CreateWaitableTimerExW, GetCurrentProcessId, GetCurrentThreadId, GetThreadTimes, OpenThread,
SetWaitableTimer, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, INFINITE, THREAD_QUERY_INFORMATION,
TIMER_ALL_ACCESS,
};
use windows_sys::Win32::UI::Controls::{HOVER_DEFAULT, WM_MOUSELEAVE};
use windows_sys::Win32::UI::Input::Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW};
@@ -542,6 +544,37 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
}
}
/// Get the main thread ID.
fn main_thread_id() -> u32 {
main_thread_id_via_snapshot().unwrap_or_else(main_thread_id_via_crt)
}
/// Get the main thread ID via the snapshot feature.
fn main_thread_id_via_snapshot() -> Option<u32> {
// Get the current process ID.
let process_id = unsafe { GetCurrentProcessId() };
// Take a snapshot of the process.
let snapshot = match ToolhelpSnapshot::new(process_id) {
Ok(snapshot) => snapshot,
Err(err) => {
tracing::error!("failed to take snapshot of process: {err}");
return None;
},
};
// Filter to threads owned by this process.
let threads = snapshot.filter(|ti| ti.process_id == process_id);
// Get the time that the thread was created.
let threadtimes = threads
.filter_map(|ti| ti.thread_time().map(move |time| (ti, time)))
.filter(|(_, time)| *time != 0);
// Identify the thread with the earliest time, since that must be the thread that called main().
threadtimes.min_by_key(|(_, time)| *time).map(|(ti, _)| ti.thread_id)
}
/// Returns the id of the main thread.
///
/// Windows has no real API to check if the current executing thread is the "main thread", unlike
@@ -560,10 +593,14 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
///
/// Full details of CRT initialization can be found here:
/// <https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-160>
fn main_thread_id() -> u32 {
///
/// notgull addendum: The above comment is a lie, we can get the total list of threads in the
/// process and figure out which thread came first. We try to use that strategy first, and use this
/// strategy as a fallback.
fn main_thread_id_via_crt() -> u32 {
static mut MAIN_THREAD_ID: u32 = 0;
// Function pointer used in CRT initialization section to set the above static field's value.
/// Function pointer used in CRT initialization section to set the above static field's value.
// Mark as used so this is not removable.
#[used]
@@ -583,6 +620,110 @@ fn main_thread_id() -> u32 {
unsafe { MAIN_THREAD_ID }
}
/// A screenshot from the toolhelp tool.
struct ToolhelpSnapshot {
/// The handle to the snapshot.
handle: OwnedHandle,
/// Are we returning the first thread entry?
first_entry: bool,
}
impl ToolhelpSnapshot {
/// Take a screenshot of the provided process.
fn new(process_id: u32) -> Result<Self, EventLoopError> {
// Take a snapshot.
let handle =
unsafe { toolhelp::CreateToolhelp32Snapshot(toolhelp::TH32CS_SNAPTHREAD, process_id) };
if handle == 0 {
return Err(EventLoopError::Os(os_error!("failed to get toolhelp snapshot")));
}
Ok(Self { handle: unsafe { OwnedHandle::from_raw_handle(handle as _) }, first_entry: true })
}
}
impl Iterator for ToolhelpSnapshot {
type Item = ThreadEntry;
fn next(&mut self) -> Option<Self::Item> {
let mut slot = mem::MaybeUninit::uninit();
// Write the size to the slot.
unsafe {
let slot: *mut toolhelp::THREADENTRY32 = slot.as_mut_ptr();
let size = ptr::addr_of_mut!((*slot).dwSize);
size.write(mem::size_of::<toolhelp::THREADENTRY32>() as _);
}
let result = if self.first_entry {
self.first_entry = false;
unsafe { toolhelp::Thread32First(self.handle.as_raw_handle() as _, slot.as_mut_ptr()) }
} else {
unsafe { toolhelp::Thread32Next(self.handle.as_raw_handle() as _, slot.as_mut_ptr()) }
};
if result != 0 {
let thread_entry = unsafe { slot.assume_init() };
Some(ThreadEntry {
thread_id: thread_entry.th32ThreadID,
process_id: thread_entry.th32OwnerProcessID,
})
} else {
None
}
}
}
#[derive(Copy, Clone)]
struct ThreadEntry {
/// The thread ID.
thread_id: u32,
/// The owner process ID.
process_id: u32,
}
impl ThreadEntry {
/// Get the time this thread was created.
fn thread_time(self) -> Option<u64> {
// Open a thread id.
let thread_handle = unsafe {
let handle = OpenThread(THREAD_QUERY_INFORMATION, 1, self.thread_id);
if handle == 0 {
return None;
}
OwnedHandle::from_raw_handle(handle as _)
};
// Get the time this thread was opened.
let file_time = unsafe {
let mut file_time = [mem::MaybeUninit::uninit(); 4];
let result = GetThreadTimes(
thread_handle.as_raw_handle() as _,
file_time[0].as_mut_ptr(),
file_time[1].as_mut_ptr(),
file_time[2].as_mut_ptr(),
file_time[3].as_mut_ptr(),
);
if result == 0 {
return None;
}
file_time[0].assume_init()
};
Some(
((file_time.dwHighDateTime as u64) << 32)
| ((file_time.dwLowDateTime as u64) & 0xffffffff),
)
}
}
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)

View File

@@ -1079,20 +1079,6 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::AudioVolumeDown => Some(0xe02e),
KeyCode::AudioVolumeMute => Some(0xe020),
KeyCode::AudioVolumeUp => Some(0xe030),
// Extra from Chromium sources:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
KeyCode::Lang4 => Some(0x0077),
KeyCode::Lang3 => Some(0x0078),
KeyCode::Undo => Some(0xe008),
KeyCode::Paste => Some(0xe00a),
KeyCode::Cut => Some(0xe017),
KeyCode::Copy => Some(0xe018),
KeyCode::Eject => Some(0xe02c),
KeyCode::Help => Some(0xe03b),
KeyCode::Sleep => Some(0xe05f),
KeyCode::WakeUp => Some(0xe063),
_ => None,
}
}
@@ -1252,20 +1238,6 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0xe02e => KeyCode::AudioVolumeDown,
0xe020 => KeyCode::AudioVolumeMute,
0xe030 => KeyCode::AudioVolumeUp,
// Extra from Chromium sources:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
0x0077 => KeyCode::Lang4,
0x0078 => KeyCode::Lang3,
0xe008 => KeyCode::Undo,
0xe00a => KeyCode::Paste,
0xe017 => KeyCode::Cut,
0xe018 => KeyCode::Copy,
0xe02c => KeyCode::Eject,
0xe03b => KeyCode::Help,
0xe05f => KeyCode::Sleep,
0xe063 => KeyCode::WakeUp,
_ => return PhysicalKey::Unidentified(NativeKeyCode::Windows(scancode as u16)),
})
}

View File

@@ -1071,7 +1071,7 @@ pub(super) struct InitData<'a> {
pub window: Option<Window>,
}
impl InitData<'_> {
impl<'a> InitData<'a> {
unsafe fn create_window(&self, window: HWND) -> Window {
// Register for touch events if applicable
{

View File

@@ -909,7 +909,7 @@ pub trait Window: AsAny + Send + Sync {
/// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`].
fn enabled_buttons(&self) -> WindowButtons;
/// Minimize the window, or put it back from the minimized state.
/// Sets the window to minimized or back
///
/// ## Platform-specific
///
@@ -945,7 +945,7 @@ pub trait Window: AsAny + Send + Sync {
/// - **iOS / Android / Web:** Unsupported.
fn is_maximized(&self) -> bool;
/// Set the window's fullscreen state.
/// Sets the window to fullscreen or back.
///
/// ## Platform-specific
///
@@ -1433,9 +1433,6 @@ impl From<ResizeDirection> for CursorIcon {
/// Fullscreen modes.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Fullscreen {
/// This changes the video mode of the monitor for fullscreen windows and,
/// if applicable, captures the monitor for exclusive use by this
/// application.
Exclusive(VideoModeHandle),
/// Providing `None` to `Borderless` will fullscreen on the current monitor.