mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
1 Commits
notgull/in
...
notgull/wi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c9dde9d2e |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
198
FEATURES.md
198
FEATURES.md
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://crates.io/crates/winit)
|
||||
[](https://docs.rs/winit)
|
||||
[](https://rust-windowing.github.io/winit/winit/index.html)
|
||||
[](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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
45
src/event.rs
45
src/event.rs
@@ -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.
|
||||
|
||||
54
src/lib.rs
54
src/lib.rs
@@ -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
|
||||
|
||||
@@ -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
|
||||
///
|
||||
|
||||
@@ -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)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(_)))
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
¢er,
|
||||
// `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(
|
||||
¢er,
|
||||
// `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(
|
||||
¢er,
|
||||
@@ -226,7 +231,12 @@ impl EventLoop {
|
||||
¢er,
|
||||
// `applicationDidReceiveMemoryWarning:`
|
||||
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
|
||||
move |_| app_state::handle_memory_warning(mtm),
|
||||
move |_| {
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::MemoryWarning),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Ok(EventLoop {
|
||||
|
||||
@@ -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))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) })
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 _);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user