mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6eb79f04c8 | ||
|
|
2bf12c74dc | ||
|
|
2998bbf7db | ||
|
|
3f82a6a90d | ||
|
|
2e610111b0 | ||
|
|
63d52aae32 | ||
|
|
5b4f97edac | ||
|
|
9135eb4024 | ||
|
|
23b3c127fd | ||
|
|
b343f45500 | ||
|
|
572d61f9ba | ||
|
|
87fc19826b | ||
|
|
11d1b7a980 | ||
|
|
5ca810ba8f | ||
|
|
2d1607b3f7 | ||
|
|
a32e232020 | ||
|
|
9b03bb7276 | ||
|
|
e39596151c | ||
|
|
5289b4f206 | ||
|
|
380dc4c451 | ||
|
|
6fbdbce6dd | ||
|
|
cafcaa2cdc | ||
|
|
e00204e626 | ||
|
|
a5b89bfe5a | ||
|
|
44052a093e | ||
|
|
40cee238e2 | ||
|
|
3dc5c42387 | ||
|
|
8c4a6ddcb4 | ||
|
|
5011a67f6d | ||
|
|
d621ab5018 | ||
|
|
966c033a6c | ||
|
|
1681410ca8 | ||
|
|
a82327c73f | ||
|
|
e71f765dea | ||
|
|
0738528931 | ||
|
|
8119c72d64 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "deps/apk-builder"]
|
||||
path = deps/apk-builder
|
||||
url = https://github.com/rust-windowing/android-rs-glue
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -11,6 +11,39 @@ Unreleased` header.
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 0.29.8
|
||||
|
||||
- On X11, fix IME input lagging behind.
|
||||
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
|
||||
- On X11, fix keymap not updated from xmodmap.
|
||||
- On X11, reduce the amount of time spent fetching screen resources.
|
||||
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
|
||||
- On Wayland, fix `Window::inner_size` not using the correct rounding.
|
||||
|
||||
# 0.29.7
|
||||
|
||||
- On X11, fix `Xft.dpi` reload during runtime.
|
||||
- On X11, fix window minimize.
|
||||
|
||||
# 0.29.6
|
||||
|
||||
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
|
||||
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
|
||||
|
||||
# 0.29.5
|
||||
|
||||
- On macOS, remove spurious error logging when handling `Fn`.
|
||||
- On X11, fix an issue where floating point data from the server is
|
||||
misinterpreted during a drag and drop operation.
|
||||
- On X11, fix a bug where focusing the window would panic.
|
||||
- On macOS, fix `refresh_rate_millihertz`.
|
||||
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
||||
- On X11, fix `Xft.dpi` detection from Xresources.
|
||||
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
|
||||
- On Wayland, fix resize being sent on focus change.
|
||||
- On Windows, fix `set_ime_cursor_area`.
|
||||
|
||||
# 0.29.4
|
||||
|
||||
- Fix crash when running iOS app on macOS.
|
||||
|
||||
@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
|
||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
||||
your description of the issue as detailed as possible:
|
||||
|
||||
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
|
||||
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
|
||||
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
||||
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
||||
should be supported by winit
|
||||
@@ -21,7 +21,7 @@ your description of the issue as detailed as possible:
|
||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||
|
||||
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
|
||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
||||
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
|
||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||
- you updated any relevant documentation in winit
|
||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
||||
@@ -34,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
|
||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||
should be updated.
|
||||
|
||||
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
|
||||
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
|
||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
||||
|
||||
@@ -46,27 +46,26 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
|
||||
|
||||
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
|
||||
|
||||
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||
|
||||
## Release process
|
||||
|
||||
Given that winit is a widely used library we should be able to make a patch
|
||||
Given that winit is a widely used library, we should be able to make a patch
|
||||
releases at any time we want without blocking the development of new features.
|
||||
|
||||
To achieve these goals, a new branch is created for every new release. Releases
|
||||
and later patch releases are committed and tagged in this branch.
|
||||
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
|
||||
|
||||
The exact steps for an exemplary `0.2.0` release might look like this:
|
||||
1. Initially the version on the latest master is `0.1.0`
|
||||
1. Initially, the version on the latest master is `0.1.0`
|
||||
2. A new `v0.2.x` branch is created for the release
|
||||
3. In the branch, the version is bumped to `v0.2.0`
|
||||
4. The new commit in the branch is tagged `v0.2.0`
|
||||
5. The version is pushed to crates.io
|
||||
6. A GitHub release is created for the `v0.2.0` tag
|
||||
7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
|
||||
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
|
||||
|
||||
When doing a patch release the process is similar:
|
||||
1. Initially the version of the latest release is `0.2.0`
|
||||
When doing a patch release, the process is similar:
|
||||
1. Initially, the version of the latest release is `0.2.0`
|
||||
2. Checkout the `v0.2.x` branch
|
||||
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
|
||||
4. Follow steps 3-7 of the regular release example
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.29.4"
|
||||
version = "0.29.8"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2021"
|
||||
@@ -66,7 +66,7 @@ log = "0.4"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
once_cell = "1.12"
|
||||
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
|
||||
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
|
||||
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
|
||||
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
smol_str = "0.2.0"
|
||||
@@ -160,13 +160,13 @@ memmap2 = { version = "0.9.0", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
|
||||
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true }
|
||||
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
|
||||
wayland-client = { version = "0.31.1", optional = true }
|
||||
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
|
||||
16
FEATURES.md
16
FEATURES.md
@@ -1,6 +1,6 @@
|
||||
# Winit Scope
|
||||
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling, and can
|
||||
Winit aims to expose an interface that abstracts over window creation and input handling and can
|
||||
be used to create both games and applications. It supports the following main graphical platforms:
|
||||
- Desktop
|
||||
- Windows 7+ (10+ is tested regularly)
|
||||
@@ -45,10 +45,10 @@ be released and the library will enter maintenance mode. For the most part, new
|
||||
be added past this point. New platform features may be accepted and exposed through point releases.
|
||||
|
||||
### Tier upgrades
|
||||
Some platform features could in theory be exposed across multiple platforms, but have not gone
|
||||
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
|
||||
through the implementation work necessary to function on all platforms. When one of these features
|
||||
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 gets deprecated and become permanently
|
||||
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
||||
exposed through the core, cross-platform API.
|
||||
|
||||
# Features
|
||||
@@ -88,7 +88,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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
|
||||
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
|
||||
@@ -105,7 +105,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
- **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 icon**: Changing the cursor icon or hiding the cursor.
|
||||
- **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.
|
||||
@@ -151,12 +151,12 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* Valid orientations
|
||||
* Home indicator visibility
|
||||
* Status bar visibility and style
|
||||
* Deferrring system gestures
|
||||
* Deferring system gestures
|
||||
* Getting the device idiom
|
||||
* Getting the preferred video mode
|
||||
|
||||
### Web
|
||||
* Get if systems preferred color scheme is "dark"
|
||||
* Get if the systems preferred color scheme is "dark"
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
@@ -166,7 +166,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
Legend:
|
||||
|
||||
- ✔️: Works as intended
|
||||
- ▢: Mostly works but some bugs are known
|
||||
- ▢: Mostly works, but some bugs are known
|
||||
- ❌: Missing feature or large bugs making it unusable
|
||||
- **N/A**: Not applicable for this platform
|
||||
- ❓: Unknown status
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
The winit maintainers would like to recognize the following former winit
|
||||
contributors, without whom winit would not exist in its current form. We thank
|
||||
them deeply for their time and efforts, and wish them best of luck in their
|
||||
them deeply for their time and efforts and wish them the best of luck in their
|
||||
future endeavors:
|
||||
|
||||
* [@tomaka]: For creating the winit project and guiding it through its early
|
||||
years of existence.
|
||||
* [@vberger]: For diligently creating the Wayland backend, and being its
|
||||
* [@vberger]: For diligently creating the Wayland backend and being its
|
||||
extremely helpful and benevolent maintainer for years.
|
||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||
winit backend, and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
|
||||
winit backend and standardizing HiDPI support across all of them.
|
||||
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a
|
||||
vastly more sustainable era of winit.
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
|
||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
|
||||
stellar dedication to improving both winit and glutin.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend, and his
|
||||
immense involvement in designing and implementing the new keyboard API.
|
||||
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API.
|
||||
|
||||
[@tomaka]: https://github.com/tomaka
|
||||
[@vberger]: https://github.com/vberger
|
||||
|
||||
29
README.md
29
README.md
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.29.4"
|
||||
winit = "0.29.8"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -26,7 +26,7 @@ Join us in any of these:
|
||||
|
||||
Winit is a window creation and management library. It can create windows and lets you handle
|
||||
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
|
||||
produced by window.
|
||||
produced by the window.
|
||||
|
||||
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
|
||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||
@@ -42,7 +42,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to
|
||||
the MSRV will be accompanied by a minor version bump.
|
||||
|
||||
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||
@@ -53,12 +53,11 @@ min(sid, stable - 3)
|
||||
```
|
||||
|
||||
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
|
||||
`stable` is the latest stable version of Rust. This bound may be broken in the
|
||||
event of a major ecosystem shift or a security vulnerability.
|
||||
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
|
||||
|
||||
[Debian Sid]: https://packages.debian.org/sid/rustc
|
||||
|
||||
The exception to this is for the Android platform, where a higher Rust version
|
||||
The exception is for the Android platform, where a higher Rust version
|
||||
must be used for certain Android features. In this case, the MSRV will be
|
||||
capped at the latest stable version of Rust minus three. This inconsistency is
|
||||
not reflected in Cargo metadata, as it is not powerful enough to expose this
|
||||
@@ -86,7 +85,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
|
||||
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||
insert it into the DOM yourself.
|
||||
|
||||
For example code using Winit with WebAssembly, check out the [web example]. For
|
||||
For the example code using Winit with WebAssembly, check out the [web example]. For
|
||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||
book].
|
||||
|
||||
@@ -109,7 +108,7 @@ glue crate (prior to `0.28` it used
|
||||
|
||||
The version of the glue crate that your application depends on _must_ match the
|
||||
version that Winit depends on because the glue crate is responsible for your
|
||||
application's main entrypoint. If Cargo resolves multiple versions they will
|
||||
application's main entry point. If Cargo resolves multiple versions, they will
|
||||
clash.
|
||||
|
||||
`winit` glue compatibility table:
|
||||
@@ -127,7 +126,7 @@ The recommended way to avoid a conflict with the glue version is to avoid explic
|
||||
depending on the `android-activity` crate, and instead consume the API that
|
||||
is re-exported by Winit under `winit::platform::android::activity::*`
|
||||
|
||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
||||
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
|
||||
|
||||
```toml
|
||||
[lib]
|
||||
@@ -135,14 +134,14 @@ name = "main"
|
||||
crate-type = ["cdylib"]
|
||||
```
|
||||
|
||||
All Android applications are based on an `Activity` subclass and the
|
||||
All Android applications are based on an `Activity` subclass, and the
|
||||
`android-activity` crate is designed to support different choices for this base
|
||||
class. Your application _must_ specify the base class it needs via a feature flag:
|
||||
|
||||
| Base Class | Feature Flag | Notes |
|
||||
| :--------------: | :---------------: | :-----: |
|
||||
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||
|
||||
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
|
||||
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
|
||||
@@ -155,9 +154,9 @@ For more details, refer to these `android-activity` [example applications](https
|
||||
|
||||
##### Converting from `ndk-glue` to `android-activity`
|
||||
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
|
||||
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
|
||||
1. Remove `ndk-glue` from your `Cargo.toml`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.4", features = [ "android-native-activity" ] }`
|
||||
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.8", features = [ "android-native-activity" ] }`
|
||||
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
|
||||
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||
|
||||
@@ -173,7 +172,7 @@ If you encounter problems, you should try doing your initialization inside
|
||||
#### iOS
|
||||
|
||||
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
|
||||
by all UI related code, see issue [#1705]. You should consider creating your windows
|
||||
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
|
||||
inside `Event::Resumed`.
|
||||
|
||||
|
||||
@@ -184,5 +183,5 @@ inside `Event::Resumed`.
|
||||
|
||||
#### Redox OS
|
||||
|
||||
Redox OS has some functionality not present yet, that will be implemented when
|
||||
Redox OS has some functionality not yet present that will be implemented when
|
||||
its orbital display server provides it.
|
||||
|
||||
@@ -5,7 +5,7 @@ These images are used in the documentation of `winit`.
|
||||
## keyboard_*.svg
|
||||
|
||||
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is
|
||||
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
|
||||
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
|
||||
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
|
||||
which have been released under the same license as a derivative work.
|
||||
|
||||
56
examples/focus.rs
Normal file
56
examples/focus.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
//! Example for focusing a window.
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
#[cfg(not(wasm_platform))]
|
||||
use std::time;
|
||||
#[cfg(wasm_platform)]
|
||||
use web_time as time;
|
||||
use winit::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut deadline = time::Instant::now() + time::Duration::from_secs(3);
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
||||
// Timeout reached; focus the window.
|
||||
println!("Re-focusing the window.");
|
||||
deadline += time::Duration::from_secs(3);
|
||||
window.focus_window();
|
||||
}
|
||||
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
|
||||
WindowEvent::CloseRequested => elwt.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
// Notify the windowing system that we'll be presenting to the window.
|
||||
window.pre_present_notify();
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::AboutToWait => {
|
||||
window.request_redraw();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline));
|
||||
})
|
||||
}
|
||||
@@ -7,17 +7,30 @@
|
||||
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
|
||||
//! also be used to fill the window buffer, but they are more complicated to use.
|
||||
|
||||
use winit::window::Window;
|
||||
#[allow(unused_imports)]
|
||||
pub use platform::cleanup_window;
|
||||
pub use platform::fill_window;
|
||||
|
||||
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
|
||||
pub(super) fn fill_window(window: &Window) {
|
||||
use softbuffer::{Context, Surface};
|
||||
mod platform {
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use softbuffer::{Context, Surface};
|
||||
use winit::window::Window;
|
||||
use winit::window::WindowId;
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
||||
}
|
||||
|
||||
/// The graphics context used to draw to a window.
|
||||
struct GraphicsContext {
|
||||
/// The global softbuffer context.
|
||||
@@ -35,55 +48,69 @@ pub(super) fn fill_window(window: &Window) {
|
||||
}
|
||||
}
|
||||
|
||||
fn surface(&mut self, w: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(w.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, w) }
|
||||
fn create_surface(&mut self, window: &Window) -> &mut Surface {
|
||||
self.surfaces.entry(window.id()).or_insert_with(|| {
|
||||
unsafe { Surface::new(&self.context, window) }
|
||||
.expect("Failed to create a softbuffer surface")
|
||||
})
|
||||
}
|
||||
|
||||
fn destroy_surface(&mut self, window: &Window) {
|
||||
self.surfaces.remove(&window.id());
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
// NOTE: You should never do things like that, create context and drop it before
|
||||
// you drop the event loop. We do this for brevity to not blow up examples. We use
|
||||
// ManuallyDrop to prevent destructors from running.
|
||||
//
|
||||
// A static, thread-local map of graphics contexts to open windows.
|
||||
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
|
||||
pub fn fill_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.create_surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
|
||||
surface
|
||||
.resize(width, height)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
}
|
||||
|
||||
GC.with(|gc| {
|
||||
let size = window.inner_size();
|
||||
let (Some(width), Some(height)) =
|
||||
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Either get the last context used or create a new one.
|
||||
let mut gc = gc.borrow_mut();
|
||||
let surface = gc
|
||||
.get_or_insert_with(|| GraphicsContext::new(window))
|
||||
.surface(window);
|
||||
|
||||
// Fill a buffer with a solid color.
|
||||
const DARK_GRAY: u32 = 0xFF181818;
|
||||
|
||||
surface
|
||||
.resize(width, height)
|
||||
.expect("Failed to resize the softbuffer surface");
|
||||
|
||||
let mut buffer = surface
|
||||
.buffer_mut()
|
||||
.expect("Failed to get the softbuffer buffer");
|
||||
buffer.fill(DARK_GRAY);
|
||||
buffer
|
||||
.present()
|
||||
.expect("Failed to present the softbuffer buffer");
|
||||
})
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(window: &Window) {
|
||||
GC.with(|gc| {
|
||||
let mut gc = gc.borrow_mut();
|
||||
if let Some(context) = gc.as_mut() {
|
||||
context.destroy_surface(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
|
||||
pub(super) fn fill_window(_window: &Window) {
|
||||
// No-op on mobile platforms.
|
||||
mod platform {
|
||||
pub fn fill_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup_window(_window: &winit::window::Window) {
|
||||
// No-op on mobile platforms.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
window_id,
|
||||
} if window.id() == window_id => {
|
||||
println!("--------------------------------------------------------- Window {idx} CloseRequested");
|
||||
fill::cleanup_window(window);
|
||||
app.window = None;
|
||||
}
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
|
||||
38
src/dpi.rs
38
src/dpi.rs
@@ -4,13 +4,13 @@
|
||||
//!
|
||||
//! Modern computer screens don't have a consistent relationship between resolution and size.
|
||||
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
|
||||
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
|
||||
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
|
||||
//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
|
||||
//! desktop nor mobile screens have consistent resolutions within their own size classes - common
|
||||
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
|
||||
//! and beyond.
|
||||
//!
|
||||
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
|
||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
|
||||
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
|
||||
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
|
||||
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
|
||||
@@ -25,12 +25,12 @@
|
||||
//!
|
||||
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
|
||||
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
|
||||
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
|
||||
//! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
|
||||
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
|
||||
//!
|
||||
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
|
||||
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
|
||||
//! usually a mistake since there's no consistent mapping between the scale factor and the screen's
|
||||
//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
|
||||
//! than any DPI-dependent units.
|
||||
//!
|
||||
//! ### Position and Size types
|
||||
@@ -42,11 +42,11 @@
|
||||
//! coordinates as input, allowing you to use the most convenient coordinate system for your
|
||||
//! particular application.
|
||||
//!
|
||||
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
|
||||
//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the
|
||||
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
||||
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
|
||||
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
|
||||
//! will truncate the fractional part of the float, rather than properly round to the nearest
|
||||
//! will truncate the fractional part of the float rather than properly round to the nearest
|
||||
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
|
||||
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
|
||||
//! int, although rounding lessens the problem.
|
||||
@@ -55,34 +55,34 @@
|
||||
//!
|
||||
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
|
||||
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
|
||||
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
|
||||
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
|
||||
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
|
||||
//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
|
||||
//! UI elements and adjust how the platform changes the window's size to reflect the new scale
|
||||
//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
|
||||
//! can be found by calling [`window.scale_factor()`].
|
||||
//!
|
||||
//! ## How is the scale factor calculated?
|
||||
//!
|
||||
//! Scale factor is calculated differently on different platforms:
|
||||
//! The scale factor is calculated differently on different platforms:
|
||||
//!
|
||||
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
|
||||
//! display settings. While users are free to select any option they want, they're only given a
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
|
||||
//! global and changing it requires logging out. See [this article][windows_1] for technical
|
||||
//! details.
|
||||
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
|
||||
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
|
||||
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
|
||||
//! the specific value varies across devices.
|
||||
//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
|
||||
//! displays. When available, the user may pick a per-monitor scaling factor from a set of
|
||||
//! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
|
||||
//! but the specific value varies across devices.
|
||||
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
|
||||
//! currently uses a three-pronged approach:
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present.
|
||||
//! + If not present, use the value set in `Xft.dpi` in Xresources.
|
||||
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
||||
//!
|
||||
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
|
||||
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
|
||||
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
|
||||
//! - **Wayland:** Scale factor is suggested by the compositor for each window individually. The
|
||||
//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
|
||||
//! monitor scale factor may differ from the window scale factor.
|
||||
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
|
||||
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
|
||||
|
||||
20
src/lib.rs
20
src/lib.rs
@@ -10,12 +10,12 @@
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Once this is done there are two ways to create a [`Window`]:
|
||||
//! Once this is done, there are two ways to create a [`Window`]:
|
||||
//!
|
||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
||||
//!
|
||||
//! The first method is the simplest, and will give you default values for everything. The second
|
||||
//! The first method is the simplest and will give you default values for everything. The second
|
||||
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
|
||||
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
|
||||
//!
|
||||
@@ -56,7 +56,7 @@
|
||||
doc = "`EventLoopExtPumpEvents::pump_events()`"
|
||||
)]
|
||||
//! [^1]. See that method's documentation for more reasons about why
|
||||
//! it's discouraged, beyond compatibility reasons.
|
||||
//! it's discouraged beyond compatibility reasons.
|
||||
//!
|
||||
//!
|
||||
//! ```no_run
|
||||
@@ -92,9 +92,9 @@
|
||||
//!
|
||||
//! // Queue a RedrawRequested event.
|
||||
//! //
|
||||
//! // You only need to call this if you've determined that you need to redraw, in
|
||||
//! // You only need to call this if you've determined that you need to redraw in
|
||||
//! // applications which do not always need to. Applications that redraw continuously
|
||||
//! // can just render here instead.
|
||||
//! // can render here instead.
|
||||
//! window.request_redraw();
|
||||
//! },
|
||||
//! Event::WindowEvent {
|
||||
@@ -118,7 +118,7 @@
|
||||
//!
|
||||
//! # Drawing on the window
|
||||
//!
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
|
||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
|
||||
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
|
||||
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
|
||||
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
||||
@@ -183,13 +183,13 @@ pub mod window;
|
||||
pub mod platform;
|
||||
|
||||
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
|
||||
/// and `Sync`, since they always excute on a single thread.
|
||||
/// and `Sync`, since they always execute on a single thread.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Winit can run only one event loop at the time and the event loop itself is tied to some thread.
|
||||
/// The objects could be send across the threads, but once passed to winit, they execute on the
|
||||
/// mean thread if the platform demands it. Thus marking such objects as `Send + Sync` is safe.
|
||||
/// Winit can run only one event loop at a time, and the event loop itself is tied to some thread.
|
||||
/// The objects could be sent across the threads, but once passed to winit, they execute on the
|
||||
/// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
|
||||
|
||||
@@ -76,6 +76,14 @@ impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
|
||||
{
|
||||
self.event_loop.window_target().clear_exit();
|
||||
self.event_loop.run_on_demand(event_handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
/// Clear exit status.
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.p.clear_exit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,6 +713,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
self.exit.set(true)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.exit.set(false)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
@@ -918,6 +918,10 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit())
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
x11_or_wayland!(match self; Self(evlp) => evlp.exit())
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use sctk::reexports::calloop_wayland_source::WaylandSource;
|
||||
use sctk::reexports::client::globals;
|
||||
use sctk::reexports::client::{Connection, QueueHandle};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::error::{EventLoopError, OsError as RootOsError};
|
||||
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
use crate::event_loop::{
|
||||
@@ -34,7 +34,7 @@ use sink::EventSink;
|
||||
|
||||
use super::state::{WindowCompositorUpdate, WinitState};
|
||||
use super::window::state::FrameCallbackState;
|
||||
use super::{DeviceId, WaylandError, WindowId};
|
||||
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
|
||||
|
||||
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
|
||||
|
||||
@@ -356,15 +356,13 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
for mut compositor_update in compositor_updates.drain(..) {
|
||||
let window_id = compositor_update.window_id;
|
||||
if let Some(scale_factor) = compositor_update.scale_factor {
|
||||
let physical_size = self.with_state(|state| {
|
||||
if compositor_update.scale_changed {
|
||||
let (physical_size, scale_factor) = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
// Set the new scale factor.
|
||||
window.set_scale_factor(scale_factor);
|
||||
let window_size = compositor_update.size.unwrap_or(window.inner_size());
|
||||
logical_to_physical_rounded(window_size, scale_factor)
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
let scale_factor = window.scale_factor();
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
(size, scale_factor)
|
||||
});
|
||||
|
||||
// Stash the old window size.
|
||||
@@ -386,30 +384,30 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
|
||||
// Resize the window when user altered the size.
|
||||
if old_physical_size != physical_size {
|
||||
self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
let new_logical_size: LogicalSize<f64> =
|
||||
physical_size.to_logical(scale_factor);
|
||||
window.request_inner_size(new_logical_size.into());
|
||||
});
|
||||
}
|
||||
|
||||
// Make it queue resize.
|
||||
compositor_update.size = Some(new_logical_size);
|
||||
// Make it queue resize.
|
||||
compositor_update.resized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(size) = compositor_update.size.take() {
|
||||
if compositor_update.resized {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let windows = state.windows.get_mut();
|
||||
let window = windows.get(&window_id).unwrap().lock().unwrap();
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let physical_size = logical_to_physical_rounded(size, scale_factor);
|
||||
|
||||
// TODO could probably bring back size reporting optimization.
|
||||
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
|
||||
|
||||
// Mark the window as needed a redraw.
|
||||
state
|
||||
@@ -420,7 +418,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
.redraw_requested
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
physical_size
|
||||
size
|
||||
});
|
||||
|
||||
callback(
|
||||
@@ -467,44 +465,44 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
|
||||
for window_id in window_ids.drain(..) {
|
||||
let request_redraw = self.with_state(|state| {
|
||||
let event = self.with_state(|state| {
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
if window_requests.get(&window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(&window_id));
|
||||
mem::drop(state.windows.get_mut().remove(&window_id));
|
||||
false
|
||||
} else {
|
||||
let mut window = state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
if window.frame_callback_state() == FrameCallbackState::Requested {
|
||||
false
|
||||
} else {
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let mut redraw_requested = window_requests
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
|
||||
// Redraw the frame while at it.
|
||||
redraw_requested |= window.refresh_frame();
|
||||
|
||||
redraw_requested
|
||||
}
|
||||
return Some(WindowEvent::Destroyed);
|
||||
}
|
||||
|
||||
let mut window = state
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
if window.frame_callback_state() == FrameCallbackState::Requested {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let mut redraw_requested = window_requests
|
||||
.get(&window_id)
|
||||
.unwrap()
|
||||
.take_redraw_requested();
|
||||
|
||||
// Redraw the frame while at it.
|
||||
redraw_requested |= window.refresh_frame();
|
||||
|
||||
redraw_requested.then_some(WindowEvent::RedrawRequested)
|
||||
});
|
||||
|
||||
if request_redraw {
|
||||
if let Some(event) = event {
|
||||
callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
event,
|
||||
},
|
||||
&self.window_target,
|
||||
);
|
||||
@@ -629,6 +627,34 @@ pub struct EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.control_flow.set(control_flow)
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
self.control_flow.get()
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
self.exit.set(Some(0))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.exit.set(None)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_exit_code(&self, code: i32) {
|
||||
self.exit.set(Some(code))
|
||||
}
|
||||
|
||||
pub(crate) fn exit_code(&self) -> Option<i32> {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
|
||||
|
||||
@@ -656,10 +682,3 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
// The default routine does floor, but we need round on Wayland.
|
||||
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
|
||||
let width = size.width as f64 * scale_factor;
|
||||
let height = size.height as f64 * scale_factor;
|
||||
(width.round(), height.round()).into()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use sctk::reexports::client::globals::{BindError, GlobalError};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalSize};
|
||||
pub use crate::platform_impl::platform::{OsError, WindowId};
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
@@ -76,3 +77,10 @@ impl DeviceId {
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.id().as_ptr() as u64)
|
||||
}
|
||||
|
||||
/// The default routine does floor, but we need round on Wayland.
|
||||
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
|
||||
let width = size.width as f64 * scale_factor;
|
||||
let height = size.height as f64 * scale_factor;
|
||||
(width.round(), height.round()).into()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use sctk::reexports::client::Proxy;
|
||||
use sctk::output::OutputData;
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::platform_impl::platform::VideoMode as PlatformVideoMode;
|
||||
|
||||
use super::event_loop::EventLoopWindowTarget;
|
||||
@@ -24,30 +23,6 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.control_flow.set(control_flow)
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
self.control_flow.get()
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
self.exit.set(Some(0))
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn set_exit_code(&self, code: i32) {
|
||||
self.exit.set(Some(code))
|
||||
}
|
||||
|
||||
pub(crate) fn exit_code(&self) -> Option<i32> {
|
||||
self.exit.get()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -22,21 +22,19 @@ use sctk::shell::WaylandSurface;
|
||||
use sctk::shm::{Shm, ShmHandler};
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
|
||||
use crate::dpi::LogicalSize;
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
use super::event_loop::sink::EventSink;
|
||||
use super::output::MonitorHandle;
|
||||
use super::seat::{
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::output::MonitorHandle;
|
||||
use crate::platform_impl::wayland::seat::{
|
||||
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
|
||||
WinitPointerDataExt, WinitSeatState,
|
||||
};
|
||||
use super::types::kwin_blur::KWinBlurManager;
|
||||
use super::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use super::types::wp_viewporter::ViewporterState;
|
||||
use super::types::xdg_activation::XdgActivationState;
|
||||
use super::window::{WindowRequests, WindowState};
|
||||
use super::{WaylandError, WindowId};
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
|
||||
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
|
||||
use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
|
||||
use crate::platform_impl::wayland::{WaylandError, WindowId};
|
||||
use crate::platform_impl::OsError;
|
||||
|
||||
/// Winit's Wayland state.
|
||||
pub struct WinitState {
|
||||
@@ -50,7 +48,7 @@ pub struct WinitState {
|
||||
pub compositor_state: Arc<CompositorState>,
|
||||
|
||||
/// The state of the subcompositor.
|
||||
pub subcompositor_state: Arc<SubcompositorState>,
|
||||
pub subcompositor_state: Option<Arc<SubcompositorState>>,
|
||||
|
||||
/// The seat state responsible for all sorts of input.
|
||||
pub seat_state: SeatState,
|
||||
@@ -124,12 +122,17 @@ impl WinitState {
|
||||
let registry_state = RegistryState::new(globals);
|
||||
let compositor_state =
|
||||
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
|
||||
let subcompositor_state = SubcompositorState::bind(
|
||||
let subcompositor_state = match SubcompositorState::bind(
|
||||
compositor_state.wl_compositor().clone(),
|
||||
globals,
|
||||
queue_handle,
|
||||
)
|
||||
.map_err(WaylandError::Bind)?;
|
||||
) {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let output_state = OutputState::new(globals, queue_handle);
|
||||
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
|
||||
@@ -151,7 +154,7 @@ impl WinitState {
|
||||
Ok(Self {
|
||||
registry_state,
|
||||
compositor_state: Arc::new(compositor_state),
|
||||
subcompositor_state: Arc::new(subcompositor_state),
|
||||
subcompositor_state: subcompositor_state.map(Arc::new),
|
||||
output_state,
|
||||
seat_state,
|
||||
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
|
||||
@@ -214,7 +217,7 @@ impl WinitState {
|
||||
|
||||
// Update the scale factor right away.
|
||||
window.lock().unwrap().set_scale_factor(scale_factor);
|
||||
self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
|
||||
self.window_compositor_updates[pos].scale_changed = true;
|
||||
} else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) {
|
||||
// Get the window, where the pointer resides right now.
|
||||
let focused_window = match pointer.pointer().winit_data().focused_window() {
|
||||
@@ -278,9 +281,7 @@ impl WindowHandler for WinitState {
|
||||
};
|
||||
|
||||
// Populate the configure to the window.
|
||||
//
|
||||
// XXX the size on the window will be updated right before dispatching the size to the user.
|
||||
let new_size = self
|
||||
self.window_compositor_updates[pos].resized |= self
|
||||
.windows
|
||||
.get_mut()
|
||||
.get_mut(&window_id)
|
||||
@@ -293,8 +294,6 @@ impl WindowHandler for WinitState {
|
||||
&self.subcompositor_state,
|
||||
&mut self.events_sink,
|
||||
);
|
||||
|
||||
self.window_compositor_updates[pos].size = Some(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,10 +387,10 @@ pub struct WindowCompositorUpdate {
|
||||
pub window_id: WindowId,
|
||||
|
||||
/// New window size.
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
pub resized: bool,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_factor: Option<f64>,
|
||||
pub scale_changed: bool,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
@@ -401,8 +400,8 @@ impl WindowCompositorUpdate {
|
||||
fn new(window_id: WindowId) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
size: None,
|
||||
scale_factor: None,
|
||||
resized: false,
|
||||
scale_changed: false,
|
||||
close_window: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ impl Window {
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.inner_size().to_physical(scale_factor)
|
||||
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -308,7 +308,7 @@ impl Window {
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let window_state = self.window_state.lock().unwrap();
|
||||
let scale_factor = window_state.scale_factor();
|
||||
window_state.outer_size().to_physical(scale_factor)
|
||||
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -31,8 +31,8 @@ use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::event::WindowEvent;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::make_wid;
|
||||
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
|
||||
use crate::platform_impl::wayland::{logical_to_physical_rounded, make_wid};
|
||||
use crate::platform_impl::WindowId;
|
||||
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
|
||||
|
||||
@@ -254,9 +254,9 @@ impl WindowState {
|
||||
&mut self,
|
||||
configure: WindowConfigure,
|
||||
shm: &Shm,
|
||||
subcompositor: &Arc<SubcompositorState>,
|
||||
subcompositor: &Option<Arc<SubcompositorState>>,
|
||||
event_sink: &mut EventSink,
|
||||
) -> LogicalSize<u32> {
|
||||
) -> bool {
|
||||
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
|
||||
// should be delivered before the first configure, thus apply it to
|
||||
// properly scale the physical sizes provided by the users.
|
||||
@@ -265,13 +265,16 @@ impl WindowState {
|
||||
self.stateless_size = self.size;
|
||||
}
|
||||
|
||||
if configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
{
|
||||
if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
|
||||
configure.decoration_mode == DecorationMode::Client
|
||||
&& self.frame.is_none()
|
||||
&& !self.csd_fails
|
||||
}) {
|
||||
match WinitFrame::new(
|
||||
&self.window,
|
||||
shm,
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
self.compositor.clone(),
|
||||
subcompositor.clone(),
|
||||
self.queue_handle.clone(),
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
@@ -316,14 +319,9 @@ impl WindowState {
|
||||
match configure.new_size {
|
||||
(Some(width), Some(height)) => {
|
||||
let (width, height) = frame.subtract_borders(width, height);
|
||||
(
|
||||
(
|
||||
width.map(|w| w.get()).unwrap_or(1),
|
||||
height.map(|h| h.get()).unwrap_or(1),
|
||||
)
|
||||
.into(),
|
||||
false,
|
||||
)
|
||||
let width = width.map(|w| w.get()).unwrap_or(1);
|
||||
let height = height.map(|h| h.get()).unwrap_or(1);
|
||||
((width, height).into(), false)
|
||||
}
|
||||
(_, _) if stateless => (self.stateless_size, true),
|
||||
_ => (self.size, true),
|
||||
@@ -349,13 +347,31 @@ impl WindowState {
|
||||
.unwrap_or(new_size.height);
|
||||
}
|
||||
|
||||
// XXX Set the configure before doing a resize.
|
||||
let new_state = configure.state;
|
||||
let old_state = self
|
||||
.last_configure
|
||||
.as_ref()
|
||||
.map(|configure| configure.state);
|
||||
|
||||
let state_change_requires_resize = old_state
|
||||
.map(|old_state| {
|
||||
!old_state
|
||||
.symmetric_difference(new_state)
|
||||
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
|
||||
.is_empty()
|
||||
})
|
||||
// NOTE: `None` is present for the initial configure, thus we must always resize.
|
||||
.unwrap_or(true);
|
||||
|
||||
// NOTE: Set the configure before doing a resize, since we query it during it.
|
||||
self.last_configure = Some(configure);
|
||||
|
||||
// XXX Update the new size right away.
|
||||
self.resize(new_size);
|
||||
|
||||
new_size
|
||||
if state_change_requires_resize || new_size != self.inner_size() {
|
||||
self.resize(new_size);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the bounds for the inner size of the surface.
|
||||
@@ -631,7 +647,7 @@ impl WindowState {
|
||||
self.resize(inner_size.to_logical(self.scale_factor()))
|
||||
}
|
||||
|
||||
self.inner_size().to_physical(self.scale_factor())
|
||||
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
|
||||
}
|
||||
|
||||
/// Resize the window to the new inner size.
|
||||
@@ -909,7 +925,7 @@ impl WindowState {
|
||||
|
||||
/// Set the IME position.
|
||||
pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// FIXME: This won't fly unless user will have a way to request IME window per seat, since
|
||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
||||
// which seat we're setting IME position.
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
@@ -940,7 +956,7 @@ impl WindowState {
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f64) {
|
||||
self.scale_factor = scale_factor;
|
||||
|
||||
// XXX when fractional scaling is not used update the buffer scale.
|
||||
// NOTE: When fractional scaling is not used update the buffer scale.
|
||||
if self.fractional_scale.is_none() {
|
||||
let _ = self.window.set_buffer_scale(self.scale_factor as _);
|
||||
}
|
||||
@@ -1088,7 +1104,7 @@ impl From<ResizeDirection> for XdgResizeEdge {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX rust doesn't allow from `Option`.
|
||||
// NOTE: Rust doesn't allow `From<Option<Theme>>`.
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
|
||||
match theme {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
os::raw::{c_char, c_int, c_long, c_ulong},
|
||||
rc::Rc,
|
||||
@@ -56,6 +56,8 @@ pub(super) struct EventProcessor<T: 'static> {
|
||||
pub(super) first_touch: Option<u64>,
|
||||
// Currently focused window belonging to this process
|
||||
pub(super) active_window: Option<xproto::Window>,
|
||||
/// Latest modifiers we've sent for the user to trigger change in event.
|
||||
pub(super) modifiers: Cell<ModifiersState>,
|
||||
pub(super) is_composing: bool,
|
||||
}
|
||||
|
||||
@@ -994,12 +996,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
|
||||
let modifiers: crate::keyboard::ModifiersState =
|
||||
self.kb_state.mods_state().into();
|
||||
if !modifiers.is_empty() {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(modifiers.into()),
|
||||
});
|
||||
}
|
||||
self.send_modifiers(modifiers, &mut callback);
|
||||
|
||||
// The deviceid for this event is for a keyboard instead of a pointer,
|
||||
// so we have to do a little extra work.
|
||||
@@ -1061,12 +1058,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// window regains focus.
|
||||
self.held_key_press = None;
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(
|
||||
ModifiersState::empty().into(),
|
||||
),
|
||||
});
|
||||
self.send_modifiers(ModifiersState::empty(), &mut callback);
|
||||
|
||||
if let Some(window) = self.with_window(window, Arc::clone) {
|
||||
window.shared_state_lock().has_focus = false;
|
||||
@@ -1280,8 +1272,14 @@ impl<T: 'static> EventProcessor<T> {
|
||||
&& (keycodes_changed || geometry_changed)
|
||||
{
|
||||
unsafe { self.kb_state.init_with_x11_keymap() };
|
||||
let modifiers = self.kb_state.mods_state();
|
||||
self.send_modifiers(modifiers.into(), &mut callback);
|
||||
}
|
||||
}
|
||||
ffi::XkbMapNotify => {
|
||||
unsafe { self.kb_state.init_with_x11_keymap() };
|
||||
self.send_modifiers(self.kb_state.mods_state().into(), &mut callback);
|
||||
}
|
||||
ffi::XkbStateNotify => {
|
||||
let xev =
|
||||
unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) };
|
||||
@@ -1289,7 +1287,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let prev_mods = self.kb_state.mods_state();
|
||||
// NOTE: Modifiers could update without a prior event updating them,
|
||||
// thus diffing the state before and after is not reliable.
|
||||
|
||||
self.kb_state.update_modifiers(
|
||||
xev.base_mods,
|
||||
xev.latched_mods,
|
||||
@@ -1298,17 +1298,8 @@ impl<T: 'static> EventProcessor<T> {
|
||||
xev.latched_group as u32,
|
||||
xev.locked_group as u32,
|
||||
);
|
||||
let new_mods = self.kb_state.mods_state();
|
||||
if prev_mods != new_mods {
|
||||
if let Some(window) = self.active_window {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::ModifiersChanged(
|
||||
Into::<ModifiersState>::into(new_mods).into(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.send_modifiers(self.kb_state.mods_state().into(), &mut callback);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -1320,7 +1311,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
|
||||
// Handle IME requests.
|
||||
if let Ok(request) = self.ime_receiver.try_recv() {
|
||||
while let Ok(request) = self.ime_receiver.try_recv() {
|
||||
let mut ime = wt.ime.borrow_mut();
|
||||
match request {
|
||||
ImeRequest::Position(window_id, x, y) => {
|
||||
@@ -1332,48 +1323,64 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
let (window, event) = match self.ime_event_receiver.try_recv() {
|
||||
Ok((window, event)) => (window as xproto::Window, event),
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
match event {
|
||||
ImeEvent::Enabled => {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Enabled),
|
||||
});
|
||||
}
|
||||
ImeEvent::Start => {
|
||||
self.is_composing = true;
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)),
|
||||
});
|
||||
}
|
||||
ImeEvent::Update(text, position) => {
|
||||
if self.is_composing {
|
||||
// Drain IME events.
|
||||
while let Ok((window, event)) = self.ime_event_receiver.try_recv() {
|
||||
let window_id = mkwid(window as xproto::Window);
|
||||
match event {
|
||||
ImeEvent::Enabled => {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))),
|
||||
window_id,
|
||||
event: WindowEvent::Ime(Ime::Enabled),
|
||||
});
|
||||
}
|
||||
ImeEvent::Start => {
|
||||
self.is_composing = true;
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)),
|
||||
});
|
||||
}
|
||||
ImeEvent::Update(text, position) => {
|
||||
if self.is_composing {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))),
|
||||
});
|
||||
}
|
||||
}
|
||||
ImeEvent::End => {
|
||||
self.is_composing = false;
|
||||
// Issue empty preedit on `Done`.
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
});
|
||||
}
|
||||
ImeEvent::Disabled => {
|
||||
self.is_composing = false;
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Ime(Ime::Disabled),
|
||||
});
|
||||
}
|
||||
}
|
||||
ImeEvent::End => {
|
||||
self.is_composing = false;
|
||||
// Issue empty preedit on `Done`.
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
});
|
||||
}
|
||||
ImeEvent::Disabled => {
|
||||
self.is_composing = false;
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window),
|
||||
event: WindowEvent::Ime(Ime::Disabled),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send modifiers for the active window.
|
||||
///
|
||||
/// The event won't be send when the `modifiers` match the previosly `sent` modifiers value.
|
||||
fn send_modifiers<F: FnMut(Event<T>)>(&self, modifiers: ModifiersState, callback: &mut F) {
|
||||
let window_id = match self.active_window {
|
||||
Some(window) => mkwid(window),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if self.modifiers.replace(modifiers) != modifiers {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(self.modifiers.get().into()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1413,6 +1420,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
F: FnMut(Event<T>),
|
||||
{
|
||||
let wt = get_xtarget(&self.target);
|
||||
wt.xconn
|
||||
.reload_database()
|
||||
.expect("failed to reload Xft database");
|
||||
|
||||
// In the future, it would be quite easy to emit monitor hotplug events.
|
||||
let prev_list = {
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
use x11_dl::xmd::CARD32;
|
||||
pub use x11_dl::{error::OpenError, xcursor::*, xinput2::*, xlib::*, xlib_xcb::*};
|
||||
|
||||
// Isn't defined by x11_dl
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const IconicState: CARD32 = 3;
|
||||
|
||||
@@ -81,6 +81,7 @@ use crate::{
|
||||
// Xinput constants not defined in x11rb
|
||||
const ALL_DEVICES: u16 = 0;
|
||||
const ALL_MASTER_DEVICES: u16 = 1;
|
||||
const ICONIC_STATE: u32 = 3;
|
||||
|
||||
type X11Source = Generic<BorrowedFd<'static>>;
|
||||
|
||||
@@ -345,6 +346,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
held_key_press: None,
|
||||
first_touch: None,
|
||||
active_window: None,
|
||||
modifiers: Default::default(),
|
||||
is_composing: false,
|
||||
};
|
||||
|
||||
@@ -363,7 +365,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
.xconn
|
||||
.select_xkb_events(
|
||||
0x100, // Use the "core keyboard device"
|
||||
xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY,
|
||||
xkb::EventType::NEW_KEYBOARD_NOTIFY
|
||||
| xkb::EventType::MAP_NOTIFY
|
||||
| xkb::EventType::STATE_NOTIFY,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -751,6 +755,10 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
self.exit.set(Some(0))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.exit.set(None)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.exit.get().is_some()
|
||||
}
|
||||
@@ -1128,3 +1136,9 @@ impl Device {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the raw X11 representation for a 32-bit floating point to a double.
|
||||
#[inline]
|
||||
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
|
||||
(fp as f64) / ((1 << 16) as f64)
|
||||
}
|
||||
|
||||
@@ -234,7 +234,8 @@ impl XConnection {
|
||||
|
||||
fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, X11Error> {
|
||||
let root = self.default_root();
|
||||
let resources = ScreenResources::from_connection(self.xcb_connection(), root)?;
|
||||
let resources =
|
||||
ScreenResources::from_connection(self.xcb_connection(), root, self.randr_version())?;
|
||||
|
||||
// Pipeline all of the get-crtc requests.
|
||||
let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
|
||||
@@ -284,22 +285,16 @@ impl XConnection {
|
||||
|
||||
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
|
||||
let mut monitors_lock = self.monitor_handles.lock().unwrap();
|
||||
(*monitors_lock)
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.map(Ok)
|
||||
.or_else(|| {
|
||||
self.query_monitor_list()
|
||||
.map(|mon_list| {
|
||||
let monitors = Some(mon_list);
|
||||
if !DISABLE_MONITOR_LIST_CACHING {
|
||||
(*monitors_lock) = monitors.clone();
|
||||
}
|
||||
monitors
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
.unwrap()
|
||||
match *monitors_lock {
|
||||
Some(ref monitors) => Ok(monitors.clone()),
|
||||
None => {
|
||||
let monitors = self.query_monitor_list()?;
|
||||
if !DISABLE_MONITOR_LIST_CACHING {
|
||||
*monitors_lock = Some(monitors.clone());
|
||||
}
|
||||
Ok(monitors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -349,10 +344,9 @@ impl ScreenResources {
|
||||
pub(crate) fn from_connection(
|
||||
conn: &impl x11rb::connection::Connection,
|
||||
root: &x11rb::protocol::xproto::Screen,
|
||||
(major_version, minor_version): (u32, u32),
|
||||
) -> Result<Self, X11Error> {
|
||||
let version = conn.randr_query_version(0, 0)?.reply()?;
|
||||
|
||||
if (version.major_version == 1 && version.minor_version >= 3) || version.major_version > 1 {
|
||||
if (major_version == 1 && minor_version >= 3) || major_version > 1 {
|
||||
let reply = conn
|
||||
.randr_get_screen_resources_current(root.root)?
|
||||
.reply()?;
|
||||
|
||||
@@ -38,7 +38,7 @@ impl XConnection {
|
||||
// Retrieve DPI from Xft.dpi property
|
||||
pub fn get_xft_dpi(&self) -> Option<f64> {
|
||||
self.database()
|
||||
.get_string("Xfi.dpi", "")
|
||||
.get_string("Xft.dpi", "")
|
||||
.and_then(|s| f64::from_str(s).ok())
|
||||
}
|
||||
pub fn get_output_info(
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
|
||||
properties::{WmHints, WmSizeHints, WmSizeHintsSpecification},
|
||||
protocol::{
|
||||
randr,
|
||||
shape::SK,
|
||||
@@ -25,7 +25,10 @@ use crate::{
|
||||
event::{Event, InnerSizeWriter, WindowEvent},
|
||||
event_loop::AsyncRequestSerial,
|
||||
platform_impl::{
|
||||
x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error},
|
||||
x11::{
|
||||
atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender,
|
||||
X11Error,
|
||||
},
|
||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
|
||||
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||
},
|
||||
@@ -984,7 +987,7 @@ impl UnownedWindow {
|
||||
xproto::EventMask::SUBSTRUCTURE_REDIRECT
|
||||
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
),
|
||||
[WmHintsState::Iconic as u32, 0, 0, 0, 0],
|
||||
[3u32, 0, 0, 0, 0],
|
||||
)
|
||||
} else {
|
||||
self.xconn.send_client_msg(
|
||||
@@ -1374,7 +1377,8 @@ impl UnownedWindow {
|
||||
self.xwindow as xproto::Window,
|
||||
xproto::AtomEnum::WM_NORMAL_HINTS,
|
||||
)?
|
||||
.reply()?;
|
||||
.reply()?
|
||||
.unwrap_or_default();
|
||||
callback(&mut normal_hints);
|
||||
normal_hints
|
||||
.set(
|
||||
@@ -1427,6 +1431,7 @@ impl UnownedWindow {
|
||||
)
|
||||
.ok()
|
||||
.and_then(|cookie| cookie.reply().ok())
|
||||
.flatten()
|
||||
.and_then(|hints| hints.size_increment)
|
||||
.map(|(width, height)| (width as u32, height as u32).into())
|
||||
}
|
||||
@@ -1739,8 +1744,8 @@ impl UnownedWindow {
|
||||
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
),
|
||||
[
|
||||
(window.x as u32 + pointer.win_x as u32),
|
||||
(window.y as u32 + pointer.win_y as u32),
|
||||
(window.x as u32 + xinput_fp1616_to_float(pointer.win_x) as u32),
|
||||
(window.y as u32 + xinput_fp1616_to_float(pointer.win_y) as u32),
|
||||
action.try_into().unwrap(),
|
||||
1, // Button 1
|
||||
1,
|
||||
@@ -1782,9 +1787,9 @@ impl UnownedWindow {
|
||||
let state_type_atom = atoms[CARD32];
|
||||
let is_minimized = if let Ok(state) =
|
||||
self.xconn
|
||||
.get_property(self.xwindow, state_atom, state_type_atom)
|
||||
.get_property::<u32>(self.xwindow, state_atom, state_type_atom)
|
||||
{
|
||||
state.contains(&(ffi::IconicState as c_ulong))
|
||||
state.contains(&super::ICONIC_STATE)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -1821,6 +1826,7 @@ impl UnownedWindow {
|
||||
WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
|
||||
.ok()
|
||||
.and_then(|cookie| cookie.reply().ok())
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
wm_hints.urgent = request_type.is_some();
|
||||
|
||||
@@ -4,14 +4,19 @@ use std::{
|
||||
fmt, ptr,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc, Mutex,
|
||||
Arc, Mutex, RwLock, RwLockReadGuard,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
|
||||
use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
protocol::{randr::ConnectionExt as _, xproto},
|
||||
resource_manager,
|
||||
xcb_ffi::XCBConnection,
|
||||
};
|
||||
|
||||
/// A connection to an X server.
|
||||
pub(crate) struct XConnection {
|
||||
@@ -45,7 +50,10 @@ pub(crate) struct XConnection {
|
||||
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
|
||||
|
||||
/// The resource database.
|
||||
database: resource_manager::Database,
|
||||
database: RwLock<resource_manager::Database>,
|
||||
|
||||
/// RandR version.
|
||||
randr_version: (u32, u32),
|
||||
|
||||
pub latest_error: Mutex<Option<XError>>,
|
||||
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
|
||||
@@ -104,6 +112,13 @@ impl XConnection {
|
||||
let database = resource_manager::new_from_default(&xcb)
|
||||
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
|
||||
|
||||
// Load the RandR version.
|
||||
let randr_version = xcb
|
||||
.randr_query_version(1, 3)
|
||||
.expect("failed to request XRandR version")
|
||||
.reply()
|
||||
.expect("failed to query XRandR version");
|
||||
|
||||
Ok(XConnection {
|
||||
xlib,
|
||||
xcursor,
|
||||
@@ -115,8 +130,9 @@ impl XConnection {
|
||||
timestamp: AtomicU32::new(0),
|
||||
latest_error: Mutex::new(None),
|
||||
monitor_handles: Mutex::new(None),
|
||||
database,
|
||||
database: RwLock::new(database),
|
||||
cursor_cache: Default::default(),
|
||||
randr_version: (randr_version.major_version, randr_version.minor_version),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,6 +147,11 @@ impl XConnection {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn randr_version(&self) -> (u32, u32) {
|
||||
self.randr_version
|
||||
}
|
||||
|
||||
/// Get the underlying XCB connection.
|
||||
#[inline]
|
||||
pub fn xcb_connection(&self) -> &XCBConnection {
|
||||
@@ -159,8 +180,16 @@ impl XConnection {
|
||||
|
||||
/// Get the resource database.
|
||||
#[inline]
|
||||
pub fn database(&self) -> &resource_manager::Database {
|
||||
&self.database
|
||||
pub fn database(&self) -> RwLockReadGuard<'_, resource_manager::Database> {
|
||||
self.database.read().unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
/// Reload the resource database.
|
||||
#[inline]
|
||||
pub fn reload_database(&self) -> Result<(), super::X11Error> {
|
||||
let database = resource_manager::new_from_default(self.xcb_connection())?;
|
||||
*self.database.write().unwrap_or_else(|e| e.into_inner()) = database;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the latest timestamp.
|
||||
|
||||
@@ -209,6 +209,10 @@ impl Handler {
|
||||
self.exit.store(true, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn clear_exit(&self) {
|
||||
self.exit.store(false, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn exiting(&self) -> bool {
|
||||
self.exit.load(Ordering::Relaxed)
|
||||
}
|
||||
@@ -434,6 +438,10 @@ impl AppState {
|
||||
HANDLER.exit()
|
||||
}
|
||||
|
||||
pub fn clear_exit() {
|
||||
HANDLER.clear_exit()
|
||||
}
|
||||
|
||||
pub fn exiting() -> bool {
|
||||
HANDLER.exiting()
|
||||
}
|
||||
|
||||
@@ -88,7 +88,9 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
if result_len == 0 {
|
||||
log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length.");
|
||||
// This is fine - not all keys have text representation.
|
||||
// For instance, users that have mapped the `Fn` key to toggle
|
||||
// keyboard layouts will hit this code path.
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
let chars = String::from_utf16_lossy(&string[0..result_len as usize]);
|
||||
|
||||
@@ -116,6 +116,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
AppState::exit()
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
AppState::clear_exit()
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
AppState::exiting()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ use core_foundation::{
|
||||
base::{CFRelease, TCFType},
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
|
||||
use super::appkit::NSScreen;
|
||||
@@ -216,6 +218,12 @@ impl MonitorHandle {
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
unsafe {
|
||||
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
|
||||
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
|
||||
if refresh_rate > 0.0 {
|
||||
return Some((refresh_rate * 1000.0).round() as u32);
|
||||
}
|
||||
|
||||
let mut display_link = std::ptr::null_mut();
|
||||
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
|
||||
!= ffi::kCVReturnSuccess
|
||||
|
||||
@@ -649,6 +649,8 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));
|
||||
|
||||
canvas.on_touch_end();
|
||||
|
||||
canvas.on_context_menu(prevent_default);
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
|
||||
|
||||
@@ -5,7 +5,8 @@ use std::sync::{Arc, Mutex};
|
||||
use smol_str::SmolStr;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent,
|
||||
CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
|
||||
PointerEvent, WheelEvent,
|
||||
};
|
||||
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
@@ -42,6 +43,7 @@ pub struct Canvas {
|
||||
on_intersect: Option<IntersectionObserverHandle>,
|
||||
animation_frame_handler: AnimationFrameHandler,
|
||||
on_touch_end: Option<EventListenerHandle<dyn FnMut(Event)>>,
|
||||
on_context_menu: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||
}
|
||||
|
||||
pub struct Common {
|
||||
@@ -152,6 +154,7 @@ impl Canvas {
|
||||
on_intersect: None,
|
||||
animation_frame_handler: AnimationFrameHandler::new(window),
|
||||
on_touch_end: None,
|
||||
on_context_menu: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -446,6 +449,17 @@ impl Canvas {
|
||||
self.on_touch_end = Some(self.common.add_transient_event("touchend", |_| {}));
|
||||
}
|
||||
|
||||
pub(crate) fn on_context_menu(&mut self, prevent_default: bool) {
|
||||
self.on_context_menu = Some(self.common.add_event(
|
||||
"contextmenu",
|
||||
move |event: PointerEvent| {
|
||||
if prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&self) {
|
||||
self.common.fullscreen_handler.request_fullscreen()
|
||||
}
|
||||
@@ -524,6 +538,7 @@ impl Canvas {
|
||||
self.animation_frame_handler.cancel();
|
||||
self.on_touch_end = None;
|
||||
self.common.fullscreen_handler.cancel();
|
||||
self.on_context_menu = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use windows_sys::Win32::{
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{
|
||||
DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, MDT_EFFECTIVE_DPI,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
||||
MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
},
|
||||
WindowsAndMessaging::IsProcessDPIAware,
|
||||
},
|
||||
@@ -21,8 +21,6 @@ use crate::platform_impl::platform::util::{
|
||||
SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT,
|
||||
};
|
||||
|
||||
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4;
|
||||
|
||||
pub fn become_dpi_aware() {
|
||||
static ENABLE_DPI_AWARENESS: Once = Once::new();
|
||||
ENABLE_DPI_AWARENESS.call_once(|| {
|
||||
|
||||
@@ -21,7 +21,7 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE,
|
||||
Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM},
|
||||
Graphics::Gdi::{
|
||||
GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient,
|
||||
ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE,
|
||||
@@ -35,13 +35,9 @@ use windows_sys::Win32::{
|
||||
Input::{
|
||||
Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW},
|
||||
KeyboardAndMouse::{
|
||||
MapVirtualKeyW, ReleaseCapture, SetCapture, TrackMouseEvent, MAPVK_VK_TO_VSC_EX,
|
||||
TME_LEAVE, TRACKMOUSEEVENT, VK_NUMLOCK, VK_SHIFT,
|
||||
},
|
||||
Pointer::{
|
||||
POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE, POINTER_INFO,
|
||||
POINTER_PEN_INFO, POINTER_TOUCH_INFO,
|
||||
ReleaseCapture, SetCapture, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT,
|
||||
},
|
||||
Pointer::{POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE},
|
||||
Touch::{
|
||||
CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE,
|
||||
TOUCHEVENTF_UP, TOUCHINPUT,
|
||||
@@ -54,20 +50,19 @@ use windows_sys::Win32::{
|
||||
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos,
|
||||
TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA,
|
||||
HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN,
|
||||
PT_TOUCH, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE,
|
||||
SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER,
|
||||
WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY,
|
||||
WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION,
|
||||
WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT,
|
||||
WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN,
|
||||
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
|
||||
WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
|
||||
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
|
||||
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
|
||||
WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
|
||||
WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED,
|
||||
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
|
||||
WS_VISIBLE,
|
||||
PT_TOUCH, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
|
||||
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
|
||||
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
|
||||
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
|
||||
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE,
|
||||
WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
|
||||
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
|
||||
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
|
||||
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
|
||||
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
|
||||
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP,
|
||||
WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT,
|
||||
WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -80,8 +75,8 @@ use crate::{
|
||||
WindowEvent,
|
||||
},
|
||||
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
keyboard::{KeyCode, ModifiersState, PhysicalKey},
|
||||
platform::{pump_events::PumpStatus, scancode::PhysicalKeyExtScancode},
|
||||
keyboard::ModifiersState,
|
||||
platform::pump_events::PumpStatus,
|
||||
platform_impl::platform::{
|
||||
dark_mode::try_theme,
|
||||
dpi::{become_dpi_aware, dpi_to_scale_factor},
|
||||
@@ -103,37 +98,6 @@ use self::runner::RunnerState;
|
||||
|
||||
use super::window::set_skip_taskbar;
|
||||
|
||||
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointerId: u32,
|
||||
entriesCount: *mut u32,
|
||||
pointerCount: *mut u32,
|
||||
pointerInfo: *mut POINTER_INFO,
|
||||
) -> BOOL;
|
||||
|
||||
type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
|
||||
type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||
device: HANDLE,
|
||||
pointerDeviceRect: *mut RECT,
|
||||
displayRect: *mut RECT,
|
||||
) -> BOOL;
|
||||
|
||||
type GetPointerTouchInfo =
|
||||
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||
|
||||
type GetPointerPenInfo =
|
||||
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
|
||||
|
||||
static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
|
||||
static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
|
||||
static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||
static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||
static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));
|
||||
|
||||
pub(crate) struct WindowData<T: 'static> {
|
||||
pub window_state: Arc<Mutex<WindowState>>,
|
||||
pub event_loop_runner: EventLoopRunnerShared<T>,
|
||||
@@ -566,6 +530,10 @@ impl<T> EventLoopWindowTarget<T> {
|
||||
self.runner_shared.exit_code().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn clear_exit(&self) {
|
||||
self.runner_shared.clear_exit();
|
||||
}
|
||||
|
||||
fn exit_code(&self) -> Option<i32> {
|
||||
self.runner_shared.exit_code()
|
||||
}
|
||||
@@ -1831,9 +1799,9 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
Some(SkipPointerFrameMessages),
|
||||
Some(GetPointerDeviceRects),
|
||||
) = (
|
||||
*GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*SKIP_POINTER_FRAME_MESSAGES,
|
||||
*GET_POINTER_DEVICE_RECTS,
|
||||
*util::GET_POINTER_FRAME_INFO_HISTORY,
|
||||
*util::SKIP_POINTER_FRAME_MESSAGES,
|
||||
*util::GET_POINTER_DEVICE_RECTS,
|
||||
) {
|
||||
let pointer_id = super::loword(wparam as u32) as u32;
|
||||
let mut entries_count = 0u32;
|
||||
@@ -1915,7 +1883,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
let force = match pointer_info.pointerType {
|
||||
PT_TOUCH => {
|
||||
let mut touch_info = mem::MaybeUninit::uninit();
|
||||
GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||
match unsafe {
|
||||
GetPointerTouchInfo(
|
||||
pointer_info.pointerId,
|
||||
@@ -1931,7 +1899,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
|
||||
}
|
||||
PT_PEN => {
|
||||
let mut pen_info = mem::MaybeUninit::uninit();
|
||||
GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
util::GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| {
|
||||
match unsafe {
|
||||
GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr())
|
||||
} {
|
||||
@@ -2493,105 +2461,17 @@ unsafe fn handle_raw_input<T: 'static>(userdata: &ThreadMsgTargetData<T>, data:
|
||||
return;
|
||||
}
|
||||
|
||||
let state = if pressed { Pressed } else { Released };
|
||||
let extension = {
|
||||
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
|
||||
0xE000
|
||||
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
|
||||
0xE100
|
||||
} else {
|
||||
0x0000
|
||||
}
|
||||
};
|
||||
let scancode = if keyboard.MakeCode == 0 {
|
||||
// In some cases (often with media keys) the device reports a scancode of 0 but a
|
||||
// valid virtual key. In these cases we obtain the scancode from the virtual key.
|
||||
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
|
||||
} else {
|
||||
keyboard.MakeCode | extension
|
||||
};
|
||||
if scancode == 0xE11D || scancode == 0xE02A {
|
||||
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
|
||||
// Ctrl+NumLock.
|
||||
// This equvalence means that if the user presses Pause, the keyboard will emit two
|
||||
// subsequent keypresses:
|
||||
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
|
||||
// 2, 0x0045 - Which on its own can be interpreted as Pause
|
||||
//
|
||||
// There's another combination which isn't quite an equivalence:
|
||||
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
|
||||
// PrtSc (print screen) produces the following sequence:
|
||||
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
|
||||
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
|
||||
// its own it can be interpreted as PrtSc
|
||||
//
|
||||
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
|
||||
// that there's going to be another event coming, from which we can extract the
|
||||
// appropriate key.
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
return;
|
||||
if let Some(physical_key) = raw_input::get_keyboard_physical_key(keyboard) {
|
||||
let state = if pressed { Pressed } else { Released };
|
||||
|
||||
userdata.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(RawKeyEvent {
|
||||
physical_key,
|
||||
state,
|
||||
}),
|
||||
});
|
||||
}
|
||||
let physical_key = if keyboard.VKey == VK_NUMLOCK {
|
||||
// Historically, the NumLock and the Pause key were one and the same physical key.
|
||||
// The user could trigger Pause by pressing Ctrl+NumLock.
|
||||
// Now these are often physically separate and the two keys can be differentiated by
|
||||
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
|
||||
//
|
||||
// However in this event, both keys are reported as 0x0045 even on modern hardware.
|
||||
// Therefore we use the virtual key instead to determine whether it's a NumLock and
|
||||
// set the KeyCode accordingly.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
PhysicalKey::Code(KeyCode::NumLock)
|
||||
} else {
|
||||
PhysicalKey::from_scancode(scancode as u32)
|
||||
};
|
||||
if keyboard.VKey == VK_SHIFT {
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
match code {
|
||||
KeyCode::NumpadDecimal
|
||||
| KeyCode::Numpad0
|
||||
| KeyCode::Numpad1
|
||||
| KeyCode::Numpad2
|
||||
| KeyCode::Numpad3
|
||||
| KeyCode::Numpad4
|
||||
| KeyCode::Numpad5
|
||||
| KeyCode::Numpad6
|
||||
| KeyCode::Numpad7
|
||||
| KeyCode::Numpad8
|
||||
| KeyCode::Numpad9 => {
|
||||
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
|
||||
// wasn't active. The way this is exposed to applications by the system is that
|
||||
// the application receives a fake key release event for the shift key at the
|
||||
// moment when the numpad key is pressed, just before receiving the numpad key
|
||||
// as well.
|
||||
//
|
||||
// The issue is that in the raw device event (here), the fake shift release
|
||||
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
|
||||
// have any information to tell whether it's the left shift or the right shift
|
||||
// that needs to get the fake release (or press) event so we don't forward this
|
||||
// event to the application at all.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "The shift key overrides NumLock"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
userdata.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(RawKeyEvent {
|
||||
physical_key,
|
||||
state,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,10 @@ impl<T> EventLoopRunner<T> {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
pub fn clear_exit(&self) {
|
||||
self.exit.set(None);
|
||||
}
|
||||
|
||||
pub fn should_buffer(&self) -> bool {
|
||||
let handler = self.event_handler.take();
|
||||
let should_buffer = handler.is_none();
|
||||
|
||||
@@ -10,9 +10,9 @@ use windows_sys::Win32::{
|
||||
UI::{
|
||||
Input::Ime::{
|
||||
ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
|
||||
ImmSetCandidateWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM,
|
||||
CFS_EXCLUDE, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN,
|
||||
IACE_DEFAULT,
|
||||
ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED,
|
||||
ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM,
|
||||
GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
|
||||
},
|
||||
WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED},
|
||||
},
|
||||
@@ -124,7 +124,7 @@ impl ImeContext {
|
||||
left: x,
|
||||
top: y,
|
||||
right: x + width,
|
||||
bottom: y - height,
|
||||
bottom: y + height,
|
||||
};
|
||||
let candidate_form = CANDIDATEFORM {
|
||||
dwIndex: 0,
|
||||
@@ -132,8 +132,16 @@ impl ImeContext {
|
||||
ptCurrentPos: POINT { x, y },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
let composition_form = COMPOSITIONFORM {
|
||||
dwStyle: CFS_POINT,
|
||||
ptCurrentPos: POINT { x, y: y + height },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
|
||||
unsafe { ImmSetCandidateWindow(self.himc, &candidate_form) };
|
||||
unsafe {
|
||||
ImmSetCompositionWindow(self.himc, &composition_form);
|
||||
ImmSetCandidateWindow(self.himc, &candidate_form);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) {
|
||||
|
||||
@@ -230,37 +230,45 @@ impl MonitorHandle {
|
||||
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
||||
// fields are probably changing, but we aren't looking at those fields
|
||||
// anyway), so we're using a BTreeSet deduplicate
|
||||
let mut modes = BTreeSet::new();
|
||||
let mut i = 0;
|
||||
let mut modes = BTreeSet::<RootVideoMode>::new();
|
||||
let mod_map = |mode: RootVideoMode| mode.video_mode;
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
let mut mode: DEVMODEW = mem::zeroed();
|
||||
mode.dmSize = mem::size_of_val(&mode) as u16;
|
||||
if EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == false.into() {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
|
||||
const REQUIRED_FIELDS: u32 =
|
||||
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
|
||||
|
||||
// Use Ord impl of RootVideoMode
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode {
|
||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: mode.dmBitsPerPel as u16,
|
||||
refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
|
||||
monitor: self.clone(),
|
||||
native_video_mode: Box::new(mode),
|
||||
},
|
||||
});
|
||||
let monitor_info = match get_monitor_info(self.0) {
|
||||
Ok(monitor_info) => monitor_info,
|
||||
Err(error) => {
|
||||
log::warn!("Error from get_monitor_info: {error}");
|
||||
return modes.into_iter().map(mod_map);
|
||||
}
|
||||
};
|
||||
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let mut mode: DEVMODEW = unsafe { mem::zeroed() };
|
||||
mode.dmSize = mem::size_of_val(&mode) as u16;
|
||||
if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() {
|
||||
break;
|
||||
}
|
||||
|
||||
const REQUIRED_FIELDS: u32 =
|
||||
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
assert!(has_flag(mode.dmFields, REQUIRED_FIELDS));
|
||||
|
||||
// Use Ord impl of RootVideoMode
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode {
|
||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: mode.dmBitsPerPel as u16,
|
||||
refresh_rate_millihertz: mode.dmDisplayFrequency * 1000,
|
||||
monitor: self.clone(),
|
||||
native_video_mode: Box::new(mode),
|
||||
},
|
||||
});
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
modes.into_iter().map(|mode| mode.video_mode)
|
||||
modes.into_iter().map(mod_map)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,29 @@ use windows_sys::Win32::{
|
||||
UI::{
|
||||
Input::{
|
||||
GetRawInputData, GetRawInputDeviceInfoW, GetRawInputDeviceList,
|
||||
KeyboardAndMouse::{MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, VK_NUMLOCK, VK_SHIFT},
|
||||
RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST,
|
||||
RAWINPUTHEADER, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE, RIDI_DEVICEINFO,
|
||||
RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD,
|
||||
RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
RAWINPUTHEADER, RAWKEYBOARD, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE,
|
||||
RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID,
|
||||
RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID,
|
||||
RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_DOWN,
|
||||
RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP,
|
||||
RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_DOWN,
|
||||
RI_MOUSE_BUTTON_5_UP,
|
||||
RI_KEY_E0, RI_KEY_E1, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP,
|
||||
RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN,
|
||||
RI_MOUSE_BUTTON_3_UP, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP,
|
||||
RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{event::ElementState, event_loop::DeviceEvents, platform_impl::platform::util};
|
||||
use crate::{
|
||||
event::ElementState,
|
||||
event_loop::DeviceEvents,
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
platform::scancode::PhysicalKeyExtScancode,
|
||||
platform_impl::platform::util,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
||||
@@ -220,3 +228,99 @@ pub fn get_raw_mouse_button_state(button_flags: u32) -> [Option<ElementState>; 5
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option<PhysicalKey> {
|
||||
let extension = {
|
||||
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
|
||||
0xE000
|
||||
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
|
||||
0xE100
|
||||
} else {
|
||||
0x0000
|
||||
}
|
||||
};
|
||||
let scancode = if keyboard.MakeCode == 0 {
|
||||
// In some cases (often with media keys) the device reports a scancode of 0 but a
|
||||
// valid virtual key. In these cases we obtain the scancode from the virtual key.
|
||||
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
|
||||
} else {
|
||||
keyboard.MakeCode | extension
|
||||
};
|
||||
if scancode == 0xE11D || scancode == 0xE02A {
|
||||
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
|
||||
// Ctrl+NumLock.
|
||||
// This equvalence means that if the user presses Pause, the keyboard will emit two
|
||||
// subsequent keypresses:
|
||||
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
|
||||
// 2, 0x0045 - Which on its own can be interpreted as Pause
|
||||
//
|
||||
// There's another combination which isn't quite an equivalence:
|
||||
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing
|
||||
// PrtSc (print screen) produces the following sequence:
|
||||
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
|
||||
// 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on
|
||||
// its own it can be interpreted as PrtSc
|
||||
//
|
||||
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
|
||||
// that there's going to be another event coming, from which we can extract the
|
||||
// appropriate key.
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
return None;
|
||||
}
|
||||
let physical_key = if keyboard.VKey == VK_NUMLOCK {
|
||||
// Historically, the NumLock and the Pause key were one and the same physical key.
|
||||
// The user could trigger Pause by pressing Ctrl+NumLock.
|
||||
// Now these are often physically separate and the two keys can be differentiated by
|
||||
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
|
||||
//
|
||||
// However in this event, both keys are reported as 0x0045 even on modern hardware.
|
||||
// Therefore we use the virtual key instead to determine whether it's a NumLock and
|
||||
// set the KeyCode accordingly.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
PhysicalKey::Code(KeyCode::NumLock)
|
||||
} else {
|
||||
PhysicalKey::from_scancode(scancode as u32)
|
||||
};
|
||||
if keyboard.VKey == VK_SHIFT {
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
match code {
|
||||
KeyCode::NumpadDecimal
|
||||
| KeyCode::Numpad0
|
||||
| KeyCode::Numpad1
|
||||
| KeyCode::Numpad2
|
||||
| KeyCode::Numpad3
|
||||
| KeyCode::Numpad4
|
||||
| KeyCode::Numpad5
|
||||
| KeyCode::Numpad6
|
||||
| KeyCode::Numpad7
|
||||
| KeyCode::Numpad8
|
||||
| KeyCode::Numpad9 => {
|
||||
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
|
||||
// wasn't active. The way this is exposed to applications by the system is that
|
||||
// the application receives a fake key release event for the shift key at the
|
||||
// moment when the numpad key is pressed, just before receiving the numpad key
|
||||
// as well.
|
||||
//
|
||||
// The issue is that in the raw device event (here), the fake shift release
|
||||
// event reports the numpad key as the scancode. Unfortunately, the event doesn't
|
||||
// have any information to tell whether it's the left shift or the right shift
|
||||
// that needs to get the fake release (or press) event so we don't forward this
|
||||
// event to the application at all.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "The shift key overrides NumLock"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
|
||||
return None;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(physical_key)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use once_cell::sync::Lazy;
|
||||
use windows_sys::{
|
||||
core::{HRESULT, PCWSTR},
|
||||
Win32::{
|
||||
Foundation::{BOOL, HMODULE, HWND, RECT},
|
||||
Foundation::{BOOL, HANDLE, HMODULE, HWND, RECT},
|
||||
Graphics::Gdi::{ClientToScreen, HMONITOR},
|
||||
System::{
|
||||
LibraryLoader::{GetProcAddress, LoadLibraryA},
|
||||
@@ -21,7 +21,10 @@ use windows_sys::{
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS},
|
||||
Input::KeyboardAndMouse::GetActiveWindow,
|
||||
Input::{
|
||||
KeyboardAndMouse::GetActiveWindow,
|
||||
Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO},
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement,
|
||||
GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS,
|
||||
@@ -191,7 +194,9 @@ pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to dynamically load function pointer.
|
||||
// Helper function to dynamically load function pointer as some functions
|
||||
// may not be available on all Windows platforms supported by winit.
|
||||
//
|
||||
// `library` and `function` must be zero-terminated.
|
||||
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
|
||||
assert_eq!(library.chars().last(), Some('\0'));
|
||||
@@ -237,6 +242,26 @@ pub type AdjustWindowRectExForDpi = unsafe extern "system" fn(
|
||||
dpi: u32,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointerId: u32,
|
||||
entriesCount: *mut u32,
|
||||
pointerCount: *mut u32,
|
||||
pointerInfo: *mut POINTER_INFO,
|
||||
) -> BOOL;
|
||||
|
||||
pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
|
||||
pub type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||
device: HANDLE,
|
||||
pointerDeviceRect: *mut RECT,
|
||||
displayRect: *mut RECT,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerTouchInfo =
|
||||
unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||
|
||||
pub type GetPointerPenInfo =
|
||||
unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
|
||||
|
||||
pub static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy<Option<AdjustWindowRectExForDpi>> =
|
||||
@@ -251,3 +276,13 @@ pub static SET_PROCESS_DPI_AWARENESS: Lazy<Option<SetProcessDpiAwareness>> =
|
||||
Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness));
|
||||
pub static SET_PROCESS_DPI_AWARE: Lazy<Option<SetProcessDPIAware>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware));
|
||||
pub static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
|
||||
pub static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
|
||||
Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
|
||||
pub static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||
pub static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||
pub static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));
|
||||
|
||||
@@ -83,7 +83,7 @@ use crate::{
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub(crate) struct Window {
|
||||
/// Main handle for the window.
|
||||
window: WindowWrapper,
|
||||
window: HWND,
|
||||
|
||||
/// The current window state.
|
||||
window_state: Arc<Mutex<WindowState>>,
|
||||
@@ -127,11 +127,11 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::TRANSPARENT, transparent)
|
||||
});
|
||||
});
|
||||
@@ -141,11 +141,11 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::VISIBLE, visible)
|
||||
});
|
||||
});
|
||||
@@ -153,7 +153,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> Option<bool> {
|
||||
Some(unsafe { IsWindowVisible(self.window.0) == 1 })
|
||||
Some(unsafe { IsWindowVisible(self.window) == 1 })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -189,10 +189,10 @@ impl Window {
|
||||
let (x, y): (i32, i32) = position.to_physical::<i32>(self.scale_factor()).into();
|
||||
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -246,10 +246,10 @@ impl Window {
|
||||
|
||||
if physical_size != self.inner_size() {
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, false)
|
||||
});
|
||||
});
|
||||
@@ -284,12 +284,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::RESIZABLE, resizable)
|
||||
});
|
||||
});
|
||||
@@ -303,12 +303,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::MINIMIZABLE,
|
||||
buttons.contains(WindowButtons::MINIMIZE),
|
||||
@@ -342,14 +342,14 @@ impl Window {
|
||||
/// Returns the `hwnd` of this window.
|
||||
#[inline]
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window.0
|
||||
self.window
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_04")]
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut window_handle = rwh_04::Win32Handle::empty();
|
||||
window_handle.hwnd = self.window.0 as *mut _;
|
||||
window_handle.hwnd = self.window as *mut _;
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = hinstance as *mut _;
|
||||
rwh_04::RawWindowHandle::Win32(window_handle)
|
||||
@@ -359,7 +359,7 @@ impl Window {
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::Win32WindowHandle::empty();
|
||||
window_handle.hwnd = self.window.0 as *mut _;
|
||||
window_handle.hwnd = self.window as *mut _;
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = hinstance as *mut _;
|
||||
rwh_05::RawWindowHandle::Win32(window_handle)
|
||||
@@ -376,8 +376,7 @@ impl Window {
|
||||
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
|
||||
let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe {
|
||||
// SAFETY: Handle will never be zero.
|
||||
let window = self.window.0;
|
||||
std::num::NonZeroIsize::new_unchecked(window)
|
||||
std::num::NonZeroIsize::new_unchecked(self.window)
|
||||
});
|
||||
let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) };
|
||||
window_handle.hinstance = std::num::NonZeroIsize::new(hinstance);
|
||||
@@ -413,7 +412,7 @@ impl Window {
|
||||
}
|
||||
};
|
||||
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
@@ -423,7 +422,7 @@ impl Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine))
|
||||
.map_err(|e| ExternalError::Os(os_error!(e)));
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
@@ -432,7 +431,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let (tx, rx) = channel();
|
||||
|
||||
@@ -442,7 +441,7 @@ impl Window {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.mouse
|
||||
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, !visible))
|
||||
.set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible))
|
||||
.map_err(|e| e.to_string());
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
@@ -472,7 +471,7 @@ impl Window {
|
||||
}
|
||||
|
||||
unsafe fn handle_os_dragging(&self, wparam: WPARAM) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = self.window_state.clone();
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
@@ -500,7 +499,7 @@ impl Window {
|
||||
|
||||
unsafe {
|
||||
PostMessageW(
|
||||
window.0,
|
||||
window,
|
||||
WM_NCLBUTTONDOWN,
|
||||
wparam,
|
||||
&points as *const _ as LPARAM,
|
||||
@@ -615,10 +614,10 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest)
|
||||
});
|
||||
});
|
||||
@@ -633,7 +632,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
let is_minimized = util::is_minimized(self.hwnd());
|
||||
@@ -643,7 +642,7 @@ impl Window {
|
||||
WindowState::set_window_flags_in_place(&mut window_state.lock().unwrap(), |f| {
|
||||
f.set(WindowFlags::MINIMIZED, is_minimized)
|
||||
});
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MINIMIZED, minimized)
|
||||
});
|
||||
});
|
||||
@@ -656,12 +655,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MAXIMIZED, maximized)
|
||||
});
|
||||
});
|
||||
@@ -681,14 +680,24 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
let mut window_state_lock = window_state.lock().unwrap();
|
||||
let old_fullscreen = window_state_lock.fullscreen.clone();
|
||||
if window_state_lock.fullscreen == fullscreen {
|
||||
return;
|
||||
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
// Return if we already are in the same fullscreen mode
|
||||
_ if old_fullscreen == fullscreen => return,
|
||||
// Return if saved Borderless(monitor) is the same as current monitor when requested fullscreen is Borderless(None)
|
||||
(Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None)))
|
||||
if *monitor == monitor::current_monitor(window) =>
|
||||
{
|
||||
return
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
window_state_lock.fullscreen = fullscreen.clone();
|
||||
drop(window_state_lock);
|
||||
|
||||
@@ -751,7 +760,7 @@ impl Window {
|
||||
}
|
||||
|
||||
// Update window style
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
|
||||
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
|
||||
@@ -767,7 +776,7 @@ impl Window {
|
||||
// this needs to be called before the below fullscreen SetWindowPos as this itself
|
||||
// will generate WM_SIZE messages of the old window size that can race with what we set below
|
||||
unsafe {
|
||||
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
|
||||
taskbar_mark_fullscreen(window, fullscreen.is_some());
|
||||
}
|
||||
|
||||
// Update window bounds
|
||||
@@ -776,7 +785,7 @@ impl Window {
|
||||
// Save window bounds before entering fullscreen
|
||||
let placement = unsafe {
|
||||
let mut placement = mem::zeroed();
|
||||
GetWindowPlacement(window.0, &mut placement);
|
||||
GetWindowPlacement(window, &mut placement);
|
||||
placement
|
||||
};
|
||||
|
||||
@@ -785,7 +794,7 @@ impl Window {
|
||||
let monitor = match &fullscreen {
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window.0),
|
||||
Fullscreen::Borderless(None) => monitor::current_monitor(window),
|
||||
};
|
||||
|
||||
let position: (i32, i32) = monitor.position().into();
|
||||
@@ -793,7 +802,7 @@ impl Window {
|
||||
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
window.0,
|
||||
window,
|
||||
0,
|
||||
position.0,
|
||||
position.1,
|
||||
@@ -801,7 +810,7 @@ impl Window {
|
||||
size.1 as i32,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER,
|
||||
);
|
||||
InvalidateRgn(window.0, 0, false.into());
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -809,8 +818,8 @@ impl Window {
|
||||
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
|
||||
drop(window_state_lock);
|
||||
unsafe {
|
||||
SetWindowPlacement(window.0, &placement);
|
||||
InvalidateRgn(window.0, 0, false.into());
|
||||
SetWindowPlacement(window, &placement);
|
||||
InvalidateRgn(window, 0, false.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -820,12 +829,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MARKER_DECORATIONS, decorations)
|
||||
});
|
||||
});
|
||||
@@ -841,12 +850,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_level(&self, level: WindowLevel) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(
|
||||
WindowFlags::ALWAYS_ON_TOP,
|
||||
level == WindowLevel::AlwaysOnTop,
|
||||
@@ -895,21 +904,21 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let state = self.window_state.clone();
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let scale_factor = state.lock().unwrap().scale_factor;
|
||||
ImeContext::current(window.0).set_ime_cursor_area(spot, size, scale_factor);
|
||||
ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let state = self.window_state.clone();
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
state.lock().unwrap().ime_allowed = allowed;
|
||||
ImeContext::set_ime_allowed(window.0, allowed);
|
||||
ImeContext::set_ime_allowed(window, allowed);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -918,14 +927,13 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let active_window_handle = unsafe { GetActiveWindow() };
|
||||
if window.0 == active_window_handle {
|
||||
if window == active_window_handle {
|
||||
return;
|
||||
}
|
||||
|
||||
self.thread_executor.execute_in_thread(move || unsafe {
|
||||
let _ = &window;
|
||||
let (flags, count) = request_type
|
||||
.map(|ty| match ty {
|
||||
UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX),
|
||||
@@ -935,7 +943,7 @@ impl Window {
|
||||
|
||||
let flash_info = FLASHWINFO {
|
||||
cbSize: mem::size_of::<FLASHWINFO>() as u32,
|
||||
hwnd: window.0,
|
||||
hwnd: window,
|
||||
dwFlags: flags,
|
||||
uCount: count,
|
||||
dwTimeout: 0,
|
||||
@@ -946,7 +954,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
try_theme(self.window.0, theme);
|
||||
try_theme(self.window, theme);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -961,9 +969,9 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
let len = unsafe { GetWindowTextLengthW(self.window.0) } + 1;
|
||||
let len = unsafe { GetWindowTextLengthW(self.window) } + 1;
|
||||
let mut buf = vec![0; len as usize];
|
||||
unsafe { GetWindowTextW(self.window.0, buf.as_mut_ptr(), len) };
|
||||
unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) };
|
||||
util::decode_wide(&buf).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
@@ -975,12 +983,12 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn set_undecorated_shadow(&self, shadow: bool) {
|
||||
let window = self.window.clone();
|
||||
let window = self.window;
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let _ = &window;
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||
f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow)
|
||||
});
|
||||
});
|
||||
@@ -988,15 +996,14 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn focus_window(&self) {
|
||||
let window = self.window.clone();
|
||||
let window_flags = self.window_state_lock().window_flags();
|
||||
|
||||
let is_visible = window_flags.contains(WindowFlags::VISIBLE);
|
||||
let is_minimized = util::is_minimized(self.hwnd());
|
||||
let is_foreground = window.0 == unsafe { GetForegroundWindow() };
|
||||
let is_foreground = self.window == unsafe { GetForegroundWindow() };
|
||||
|
||||
if is_visible && !is_minimized && !is_foreground {
|
||||
unsafe { force_window_active(window.0) };
|
||||
unsafe { force_window_active(self.window) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1046,18 +1053,6 @@ impl Drop for Window {
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple non-owning wrapper around a window.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct WindowWrapper(HWND);
|
||||
|
||||
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
|
||||
// For more info see:
|
||||
// https://github.com/retep998/winapi-rs/issues/360
|
||||
// https://github.com/retep998/winapi-rs/issues/396
|
||||
unsafe impl Sync for WindowWrapper {}
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
pub(super) struct InitData<'a, T: 'static> {
|
||||
// inputs
|
||||
pub event_loop: &'a EventLoopWindowTarget<T>,
|
||||
@@ -1105,7 +1100,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
unsafe { ImeContext::set_ime_allowed(window, false) };
|
||||
|
||||
Window {
|
||||
window: WindowWrapper(window),
|
||||
window,
|
||||
window_state,
|
||||
thread_executor: self.event_loop.create_thread_executor(),
|
||||
}
|
||||
@@ -1128,7 +1123,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
|
||||
let file_drop_runner = self.event_loop.runner_shared.clone();
|
||||
let file_drop_handler = FileDropHandler::new(
|
||||
win.window.0,
|
||||
win.window,
|
||||
Box::new(move |event| {
|
||||
if let Ok(e) = event.map_nonuser_event() {
|
||||
file_drop_runner.send_event(e)
|
||||
@@ -1140,7 +1135,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void };
|
||||
|
||||
assert_eq!(
|
||||
unsafe { RegisterDragDrop(win.window.0, handler_interface_ptr) },
|
||||
unsafe { RegisterDragDrop(win.window, handler_interface_ptr) },
|
||||
S_OK
|
||||
);
|
||||
Some(file_drop_handler)
|
||||
@@ -1217,7 +1212,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||
|
||||
if attributes.fullscreen.0.is_some() {
|
||||
win.set_fullscreen(attributes.fullscreen.0.map(Into::into));
|
||||
unsafe { force_window_active(win.window.0) };
|
||||
unsafe { force_window_active(win.window) };
|
||||
} else {
|
||||
let size = attributes
|
||||
.inner_size
|
||||
|
||||
@@ -1475,10 +1475,6 @@ impl Window {
|
||||
/// Returns the monitor on which the window currently resides.
|
||||
///
|
||||
/// Returns `None` if current monitor can't be detected.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
self.window
|
||||
@@ -1489,10 +1485,6 @@ impl Window {
|
||||
///
|
||||
/// This is the same as [`EventLoopWindowTarget::available_monitors`], and is provided for convenience.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
///
|
||||
/// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
||||
@@ -1511,8 +1503,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
/// **Wayland:** Always returns `None`.
|
||||
/// **Wayland / Web:** Always returns `None`.
|
||||
///
|
||||
/// [`EventLoopWindowTarget::primary_monitor`]: crate::event_loop::EventLoopWindowTarget::primary_monitor
|
||||
#[inline]
|
||||
|
||||
Reference in New Issue
Block a user