Compare commits

...

52 Commits

Author SHA1 Message Date
Christian Duerr
7a9c17a520 Bump version to 0.22.0 (#1500)
There are two PRs I'm aware of that should be relatively trivial to get
merged, which would fix some issues. Other than those, I don't think it
makes sense to wait on anything.

 - Fix Windows crash: https://github.com/rust-windowing/winit/pull/1459
 - Fix macOS mouse reports: https://github.com/rust-windowing/winit/pull/1490

While #1459 seems pretty essential to actually make winit run, #1490 is
much less important and can probably be ignored if there aren't any
resources to merge it.
2020-03-09 16:58:54 -04:00
Kirill Chibisov
b208daa271 Revert "on MacOS, Fix not sending ReceivedCharacter event for s… (#1501)
This reverts commit 9daa0738a9.

This commit introduced other bug #1453 with likely much more common bindings,
so reverting it for now.

Fixes #1453.

Co-authored-by: Osspial <osspial@gmail.com>
2020-03-09 16:57:04 -04:00
Imberflur
e85a80dd65 Fix freeze when pressing modifier keys on Windows (#1503) 2020-03-08 01:22:53 -05:00
Osspial
b1d8ce24e9 Use i32 instead of u32 for position type in WindowEvent::Moved (#1502)
* Use i32 instead of u32 for position type in WindowEvent::Moved

* Mark change as breaking
2020-03-08 00:21:04 -05:00
David Hewitt
098fd5d602 Add ability to create Icons from embedded resources on Windows (#1410)
* Add IconExtWindows trait

* Move changelog entries to unreleased category

Co-authored-by: Osspial <osspial@gmail.com>
2020-03-07 14:42:21 -05:00
Philippe Renon
2f27f64cdb On Windows, fix request_redraw() related panics (#1461)
* On Windows, fix request_redraw() related panics

These panics were introduced by 6a330a2894

Fixes https://github.com/rust-windowing/winit/issues/1391
Fixes https://github.com/rust-windowing/winit/issues/1400
Fixes https://github.com/rust-windowing/winit/issues/1466
Probably fixes other related issues

See https://github.com/rust-windowing/winit/issues/1429

* On Windows, replace all calls to UpdateWindow by calls to InvalidateRgn

This avoids directly sending a WM_PAINT message,
which might cause buffering of RedrawRequested events.

We don't want to buffer RedrawRequested events because:
- we wan't to handle RedrawRequested during processing of WM_PAINT messages
- state transitionning is broken when handling buffered RedrawRequested events

Fixes https://github.com/rust-windowing/winit/issues/1469

* On Windows, panic if we are trying to buffer a RedrawRequested event

* On Windows, move modal loop jumpstart to set_modal_loop() method

This fixes a panic.
Note that the WM_PAINT event is now sent to the modal_redraw_method
which is more correct and avoids an unecessary redraw of the window.

Relates to but does does not fix https://github.com/rust-windowing/winit/issues/1484

* On Window, filter by paint messages when draining paint messages

This seems to prevent PeekMessage from dispatching unrelated sent messages

* Change recently added panic/assert calls with warn calls

This makes the code less panicky...

And actually, winit's Windoww callbacks should not panic
because the panic will unwind into Windows code.

It is currently undefined behavior to unwind from Rust code into foreign code.
See https://doc.rust-lang.org/std/panic/fn.catch_unwind.html

* add comments to clarify WM_PAINT handling in non modal loop

* made redraw_events_cleared more explicit and more comments
2020-03-07 14:04:24 -05:00
Christian Duerr
cbb60d29a2 Remove assertions from Windows dark mode code (#1459)
* Remove assertions from Windows dark mode code

In general, winit should never assert on anything unless it means that
it is impossible to continue the execution of the program. There are
several assertions in the Windows dark mode code where this is not the
case.

Based on surface level inspection, all existing assertions could be
easily replaced with just simple conditional checks, allowing the
execution of the program to proceed with sane default values.

Fixes #1458.

* Add changelog entry

* Format code

* Pass dark mode by mutable reference

* Format code

* Return bool instead of mutable reference

* Fix dark mode success reply

Co-Authored-By: daxpedda <daxpedda@gmail.com>

* Fix dark mode success reply

* Replace magic integers with constants

Co-authored-by: daxpedda <daxpedda@gmail.com>
2020-03-07 13:56:33 -05:00
Murarth
e707052f66 Move ModifiersChanged variant to WindowEvent (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`

* macos: Fix flags_changed for ModifiersChanged variant move

I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* macos: Fire a ModifiersChanged event on window_did_resign_key

From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent.  It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* windows: Fix build

I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS

Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.

It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.

[1]: f79f21641a

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* macos: Add a hook to update stale modifiers

Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags.  These are internally stored as a bitmask in
the latter and an enum in the former.

We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`.  That's what the hook does.

This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* Only call event_mods once when determining whether to update state

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* flags_changed: Memoize window_id collection

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* window_did_resign_key: Remove synthetic ModifiersChanged event

We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* mouse_motion: Add a call to update_potentially_stale_modifiers

Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave.  Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* key_up: Add a call to update_potentially_stale_modifiers

We also want to make sure modifiers state is synchronized here, too.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* mouse_motion: Remove update_potentially_stale_modifiers invocation

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* Retry CI

* ViewState: Promote visibility of modifiers to the macos impl

This is so that we can interact with the ViewState directly from the
WindowDelegate.

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* window_delegate: Synthetically set modifiers state to empty on resignKey

This logic is implemented similarly on other platforms, so we wish to
regain parity here.  Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.

This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).

Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>

* Check for modifiers change in window events

* Fix modifier changed on macOS

Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.

The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.

This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.

* Fix unused variable warning

* Move changelog entry into `Unreleased` section

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
Philippe Renon
71bd6e73ca windows: ignore spurious mouse move messages (#1435)
Fixes https://github.com/rust-windowing/winit/issues/1428
2020-03-06 16:15:49 -05:00
Philippe Renon
b8326f6452 In control_flow example, don't schedule a new WaitUntil if wait was cancelled (#1482) 2020-03-06 10:48:54 -07:00
HeroicKatora
ece2e70a53 Update image to 0.23 (#1485)
Also makes use of a few ergonomics improvements that were introduced or
optimized in the more recent version.
2020-03-03 16:13:53 -07:00
Murarth
2b14ec23d5 Fix GitHub Actions (#1479)
* Replaces `actions/checkout@v1` with `actions/checkout@v2` to get a bug fix
2020-02-25 09:10:31 -07:00
Murarth
9999f53329 X11: Fix deadlock when an error occurs during startup (#1475) 2020-02-19 10:38:59 -07:00
Philippe Renon
522a6e3298 fix issues in wait_until_time_or_msg function (#1423)
also removed unused return value
2020-02-18 19:27:47 -05:00
Kirill Chibisov
76d0dd7ec3 On Wayland, Hide CSD for fullscreen windows (#1473) 2020-02-18 16:58:48 -07:00
daxpedda
d1073dcecb Implement ThemeChanged for web target. (#1462)
* Implement ThemeChanged for web target.

* Add TODO upstream to stdweb.

Co-authored-by: Ryan G <ryanisaacg@users.noreply.github.com>
2020-02-17 14:25:27 -05:00
Héctor Ramón
e88e8bc194 Map UserEvent properly in Event::to_static (#1468) 2020-02-16 10:53:02 -07:00
Philippe Renon
bc29931434 Add an example that calls request_redraw() from a thread (#1467)
reproduces https://github.com/rust-windowing/winit/issues/1466
2020-02-15 11:38:29 -07:00
Philippe Renon
505f312d5f Add new example that demonstrates the different control flow schemes (#1460)
User can switch between Wait, WaitUntil and Poll modes with key '1', '2' and '3' respectivly.
User can toggle request_redraw calls with the 'R' key.

Helpful for testing all control flow modes and use of request_redraw.
2020-02-13 15:20:32 -07:00
Philippe Renon
f0093d3c54 rename dpi_factor to scale_factor where appropriate (#1463)
fixes https://github.com/rust-windowing/winit/issues/1457
2020-02-13 12:41:41 -07:00
Kirill Chibisov
83b60beba6 on Wayland, Add HiDPI cursor support (#1454)
Fixes #727.
2020-02-12 19:48:58 -07:00
hatoo
5f52d7c9d0 On macOS, Fix set_simple_screen to remember frame excluding title bar (#1430)
* On macOS, Fix `set_simple_screen` to remember frame excluding title bar

* Add CHANGELOG
2020-02-12 11:27:11 +03:00
Julien Sanchez
a1b65f7080 Ignore locale if unsupported by X11 backend (#1445)
This restores default portable 'C' locale when target locale is unsupported
by X11 backend (Xlib).

When target locale is unsupported by X11, some locale-dependent Xlib
functions like `XSetLocaleModifiers` fail or have no effect triggering
later failures and panics.

When target locale is not valid, `setLocale` should normally leave the
locale unchanged (`setLocale` returns 'C'). However, in some situations,
locale is accepted by `setLocale` (`setLocale` returns the new locale)
but the accepted locale is unsupported by Xlib (`XSupportsLocale` returns
`false`).

Fix #636
2020-02-09 22:37:06 -07:00
Kirill Chibisov
96df858961 On Wayland, fix color from close_button_icon_color not applying (#1444) 2020-02-08 19:36:44 -07:00
Kirill Chibisov
4eddd1e5bc On Wayland, fix coordinates in touch events when scale factor isn't 1 (#1439)
* On Wayland, fix coordinates in touch events when scale factor isn't 1

* Explicitly state that Wayland is using LogicalPosition internally

* Fix CHANGELOG
2020-02-08 01:25:08 -07:00
Freya Gentz
28f0eb598d Release 0.21.0 (#1440)
* Update CHANGELOG.md

* Update README.md

* Update Cargo.toml

* Update Cargo.toml

* Update README.md

* Update CHANGELOG.md
2020-02-04 19:07:31 -07:00
David Craven
c1eb7f9629 Fix deadlock wayland. (#1438) 2020-02-04 16:46:19 -07:00
Murarth
2f8aa5c52a Remove armv7-apple-ios target from CI (#1433) 2020-02-03 17:42:52 -07:00
hatoo
22dcc19898 Fix set_minimized(true) works only with decorations on macOS (#1411)
* On macOS, Fix set_minimized(true) works only with decorations

* Add CHANGELOG
2020-01-31 18:07:36 +03:00
Christian Duerr
e295104199 Remove Wayland theme intermediates (#1209)
* Remove Wayland theme intermediates

This removes the intermediate struct for passing a Wayland theme to
allow the user direct implementation of the trait.

By passing the trait directly, it is possible for downstream users to
have more freedom with customization without relying on winit to offer
these options as fields.

It should also make maintenance easier, since winit already doesn't
implement all the functions which are offered by the smithay client
toolkit.

* Reimplement SCTK's Theme and ButtonState

* Fix style issues

* Remove public signature

* Format code

* Add change log entry

Co-authored-by: Murarth <murarth@gmail.com>
2020-01-26 19:56:54 -07:00
Murarth
66fe69edd9 Fix warnings on macos (#1419) 2020-01-26 13:55:27 -07:00
Ryan G
fd946feac4 Web backend refactor and documentation (#1415)
The current implementation of the event loop runner has some significant
problems. It can't handle multiple events being emitted at once (for
example, when a keyboard event causes a key input, a text input, and a
modifier change.) It's also relatively easy to introduce bugs for the
different possible control flow states.

The new model separates intentionally emitting a NewEvents (poll
completed, wait completed, init) and emitting a normal event, as well as
providing a method for emitting multiple events in a single call.
2020-01-25 19:04:03 -05:00
Freya Gentz
8856b6ecb7 Remove unused code in X11 backend. (#1416)
Signed-off-by: Freya Gentz <zegentzy@protonmail.com>
2020-01-23 12:42:15 -07:00
Osspial
0ae78db6cb Fix building on Windows 7 and 8 (#1398)
* Fix building on Windows 7 and 8

* Format
2020-01-21 12:43:36 -07:00
David Yamnitsky
3e3bb8a8f1 add hide_application on macos (#1364)
Co-authored-by: Osspial <osspial@gmail.com>
Co-authored-by: Bogaevsky <vbogaevsky@gmail.com>
2020-01-19 16:38:52 -07:00
Steven Sheldon
e48262a797 Simplify code by switching to higher-level dispatch APIs (#1409)
* Switch to higher-level dispatch APIs

* Inline all the functions

* Switch to autoreleasepool from objc
2020-01-19 11:47:55 -07:00
Diggory Hardy
d934f94704 Fix: deadlock when requesting redraw on X11 (#1408) 2020-01-18 10:49:02 -07:00
Ryan G
1fe4a7a4ea Add the ability to pass a prebuilt canvas (#1394)
This allows Winit to take control of existing canvas elements in the
DOM, which is useful for web applications with other content in the
page.
2020-01-15 21:20:14 -05:00
hatoo
9daa0738a9 on MacOS, Fix not sending ReceivedCharacter event for some key combination (#1347)
* MacOS FIX #1267

* Add CHANGELOG

* Remove unnecessary trace!
2020-01-15 00:52:18 +03:00
Philippe Renon
ad7d4939a8 doc: change remaining EventsCleared references to MainEventsCleared (#1390) 2020-01-13 12:15:44 -07:00
Ryan G
c4d07952cb Remove TODOs from the web backen (#1395) 2020-01-13 12:14:25 -07:00
Héctor Ramón
dc302b0db4 Return physical position in CursorMoved on macOS (#1378) 2020-01-12 20:50:34 +03:00
Kirill Chibisov
a6d180cefb On Wayland, fix coordinates in mouse events when scale factor isn't 1 (#1385)
* On Wayland, fix coordinates in mouse events when scale factor isn't 1

* Refactor mouse_focus to be a surface instead of WindowId
2020-01-11 01:45:52 -07:00
Francesca Plebani
1ddceeb063 macOS: Unbundled window activation hack (#1318)
* `ns_string_id_ref` convenience fn

* Unbundled app activation hack

* Greatly improved solution

* Shortened names

* Remove cruft I left here a year ago

* Doc improvements

* Use `AtomicBool` for `activationHackFlag`

* Add CHANGELOG entry

* Doc improvements

* Fix `did_finish_launching` return type + delay more

* More reliable activation checking

* Fix merge goof

* ...fix other merge goof

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Bogaevsky <vbogaevsky@gmail.com>
2020-01-10 16:02:42 -08:00
hatoo
633d0deeae Fix run_return does not return on macOS unless it receives a message (#1380)
* On MacOS, fix `run_return` not exit immediately

* Add CHANGELOG
2020-01-10 18:25:55 +03:00
Murarth
9e3844ddd9 Fix warnings on all platforms (#1383)
* Fix warnings on all platforms

* Also fixed a trait impl bound that I noticed along the way
2020-01-09 22:29:31 -07:00
Benjamin Saunders
4b618bd6a6 Don't discard high-precision cursor position data (#1375)
* Don't discard high-precision cursor position data

Most platforms (X11, wayland, macos, stdweb, ...) provide physical
positions in f64 units, which can contain meaningful fractional
data. For example, this can be empirically observed on modern X11
using a typical laptop touchpad. This is useful for e.g. content
creation tools, where cursor motion might map to brush strokes on a
canvas with higher-than-screen resolution, or positioning of an object
in a vector space.

* Update CHANGELOG.md

Co-Authored-By: Murarth <murarth@gmail.com>

Co-authored-by: Murarth <murarth@gmail.com>
2020-01-09 21:19:50 -07:00
Osspial
09c4ed0694 Remove util from gitignore 2020-01-09 12:24:57 -05:00
Murarth
d15eb04f9e Make docs set control_flow in a more realistic way (#1376) 2020-01-07 22:55:18 -05:00
Bastian Kauschke
02ac7456e4 impl Default for WindowBuilder (#1373) 2020-01-07 14:33:56 -05:00
Murarth
6b0875728c X11: Fix deadlock on window state with certain window events (#1369) 2020-01-06 20:54:22 -07:00
Osspial
6a330a2894 On Windows, fix bug where RedrawRequested would only get emitted every other iteration of the event loop (#1366)
* Fix bug causing RedrawRequested events to only get emitted every other iteration of the event loop.

* Initialize simple_logger in examples.

This PR's primary bug was discovered because a friend of mine reported
that winit was emitting concerning log messages, which I'd never seen
since none of the examples print out the log messages. This addresses
that, to hopefully reduce the chance of bugs going unnoticed in the
future.

* Add changelog entry

* Format
2020-01-06 15:28:58 -05:00
90 changed files with 2241 additions and 1557 deletions

View File

@@ -17,7 +17,7 @@ jobs:
Check_Formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
@@ -39,7 +39,6 @@ jobs:
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: armv7-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
@@ -55,7 +54,7 @@ jobs:
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1

View File

@@ -9,7 +9,7 @@ jobs:
Publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@ Cargo.lock
target/
rls/
.vscode/
util/
*~
*.wasm
*.ts

View File

@@ -1,4 +1,40 @@
# Unreleased
# 0.22.0 (2020-03-07)
- On Windows, fix minor timing issue in wait_until_time_or_msg
- On Windows, rework handling of request_redraw() to address panics.
- On macOS, fix `set_simple_screen` to remember frame excluding title bar.
- On Wayland, fix coordinates in touch events when scale factor isn't 1.
- On Wayland, fix color from `close_button_icon_color` not applying.
- Ignore locale if unsupported by X11 backend
- On Wayland, Add HiDPI cursor support
- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change.
- Fix `Event::to_static` returning `None` for user events.
- On Wayland, Hide CSD for fullscreen windows.
- On Windows, ignore spurious mouse move messages.
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
- Add `BadIcon::OsError` variant for when OS icon functionality fails
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
# 0.21.0 (2020-02-04)
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
- On macOS, fix set_minimized(true) works only with decorations.
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
- On macOS, fix `run_return` does not return unless it receives a message.
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
- On X11, fix deadlock on window state when handling certain window events.
- `WindowBuilder` now implements `Default`.
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
- On Web, add the ability to provide a custom canvas
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
# 0.20.0 (2020-01-05)
@@ -20,7 +56,7 @@
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
- On X11, fix `CursorEntered` event being generated for non-winit windows.
- On macOS, fix crash when starting maximized without decorations.
- On macOS, fix application not to terminate on `run_return`.
- On macOS, fix application not terminating on `run_return`.
- On Wayland, fix cursor icon updates on window borders when using CSD.
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.20.0"
version = "0.22.0"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
@@ -28,8 +28,8 @@ raw-window-handle = "0.3"
bitflags = "1"
[dev-dependencies]
image = "0.21"
env_logger = "0.5"
image = "0.23"
simple_logger = "1"
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
@@ -41,7 +41,7 @@ objc = "0.2.3"
cocoa = "0.19.1"
core-foundation = "0.6"
core-graphics = "0.17.3"
dispatch = "0.1.4"
dispatch = "0.2.0"
objc = "0.2.6"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
@@ -77,7 +77,7 @@ features = [
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
mio = "0.6"
mio-extras = "2.0"
smithay-client-toolkit = "0.6"
smithay-client-toolkit = "^0.6.6"
x11-dl = "2.18.3"
percent-encoding = "2.0"
@@ -101,6 +101,8 @@ features = [
'HtmlCanvasElement',
'HtmlElement',
'KeyboardEvent',
'MediaQueryList',
'MediaQueryListEvent',
'MouseEvent',
'Node',
'PointerEvent',

View File

@@ -149,6 +149,9 @@ If your PR makes notable changes to Winit's features, please update this section
* Getting the device idiom
* Getting the preferred video mode
### Web
* Get if systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)

View File

@@ -7,7 +7,7 @@
```toml
[dependencies]
winit = "0.20.0"
winit = "0.22.0"
```
## [Documentation](https://docs.rs/winit)
@@ -46,12 +46,14 @@ fn main() {
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Wait,
_ => (),
}
});
}

119
examples/control_flow.rs Normal file
View File

@@ -0,0 +1,119 @@
use std::{thread, time};
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Wait,
WaitUntil,
Poll,
}
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() {
simple_logger::init().unwrap();
println!("Press '1' to switch to Wait mode.");
println!("Press '2' to switch to WaitUntil mode.");
println!("Press '3' to switch to Poll mode.");
println!("Press 'R' to toggle request_redraw() calls.");
println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop)
.unwrap();
let mut mode = Mode::Wait;
let mut request_redraw = false;
let mut wait_cancelled = false;
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
use winit::event::{ElementState, StartCause, VirtualKeyCode};
println!("{:?}", event);
match event {
Event::NewEvents(start_cause) => {
wait_cancelled = mode == Mode::WaitUntil;
match start_cause {
StartCause::ResumeTimeReached {
start: _,
requested_resume: _,
} => {
wait_cancelled = false;
}
_ => (),
}
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => {
close_requested = true;
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: ElementState::Pressed,
..
},
..
} => match virtual_code {
VirtualKeyCode::Key1 => {
mode = Mode::Wait;
println!("\nmode: {:?}\n", mode);
}
VirtualKeyCode::Key2 => {
mode = Mode::WaitUntil;
println!("\nmode: {:?}\n", mode);
}
VirtualKeyCode::Key3 => {
mode = Mode::Poll;
println!("\nmode: {:?}\n", mode);
}
VirtualKeyCode::R => {
request_redraw = !request_redraw;
println!("\nrequest_redraw: {}\n", request_redraw);
}
VirtualKeyCode::Escape => {
close_requested = true;
}
_ => (),
},
_ => (),
},
Event::MainEventsCleared => {
if request_redraw && !wait_cancelled && !close_requested {
window.request_redraw();
}
if close_requested {
*control_flow = ControlFlow::Exit;
}
}
Event::RedrawRequested(_window_id) => {}
Event::RedrawEventsCleared => {
*control_flow = match mode {
Mode::Wait => ControlFlow::Wait,
Mode::WaitUntil => {
if wait_cancelled {
*control_flow
} else {
ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME)
}
}
Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME);
ControlFlow::Poll
}
};
}
_ => (),
}
});
}

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@@ -37,6 +38,7 @@ fn main() {
_ => (),
}
}
WindowEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
@@ -45,7 +47,6 @@ fn main() {
ElementState::Pressed => println!("mouse button {} pressed", button),
ElementState::Released => println!("mouse button {} released", button),
},
DeviceEvent::ModifiersChanged(m) => modifiers = m,
_ => (),
},
_ => (),

View File

@@ -11,6 +11,7 @@ fn main() {
Timer,
}
simple_logger::init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let _window = WindowBuilder::new()

View File

@@ -5,6 +5,7 @@ use winit::monitor::{MonitorHandle, VideoMode};
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -5,6 +5,7 @@ use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,6 +1,7 @@
use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();

View File

@@ -1,7 +1,5 @@
#[cfg(not(target_arch = "wasm32"))]
fn main() {
extern crate env_logger;
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{
@@ -14,7 +12,7 @@ fn main() {
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
env_logger::init();
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut windows = HashMap::new();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -0,0 +1,39 @@
use std::{thread, time};
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
thread::spawn(move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
});
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
}
_ => (),
}
});
}

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut resizable = false;

View File

@@ -7,6 +7,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -1,6 +1,7 @@
use winit::event_loop::EventLoop;
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let monitor = event_loop.primary_monitor();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -8,6 +8,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -7,6 +7,8 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
@@ -43,13 +45,11 @@ fn main() {
fn load_icon(path: &Path) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path).expect("Failed to open icon path");
use image::{GenericImageView, Pixel};
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba();
let (width, height) = image.dimensions();
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
for (_, _, pixel) in image.pixels() {
rgba.extend_from_slice(&pixel.to_rgba().data);
}
let rgba = image.into_raw();
(rgba, width, height)
};
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")

View File

@@ -18,6 +18,7 @@ fn main() {
};
let mut event_loop = EventLoop::new();
simple_logger::init().unwrap();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
@@ -27,6 +28,8 @@ fn main() {
while !quit {
event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{:?}", event);
@@ -42,7 +45,7 @@ fn main() {
Event::MainEventsCleared => {
*control_flow = ControlFlow::Exit;
}
_ => *control_flow = ControlFlow::Wait,
_ => (),
}
});

View File

@@ -151,8 +151,8 @@ impl Pixel for f64 {
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
/// otherwise, you risk panics.
#[inline]
pub fn validate_scale_factor(dpi_factor: f64) -> bool {
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
pub fn validate_scale_factor(scale_factor: f64) -> bool {
scale_factor.is_sign_positive() && scale_factor.is_normal()
}
/// A position represented in logical pixels.
@@ -178,16 +178,16 @@ impl<P: Pixel> LogicalPosition<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
physical: T,
dpi_factor: f64,
scale_factor: f64,
) -> Self {
physical.into().to_logical(dpi_factor)
physical.into().to_logical(scale_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<X> {
assert!(validate_scale_factor(dpi_factor));
let x = self.x.into() * dpi_factor;
let y = self.y.into() * dpi_factor;
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<X> {
assert!(validate_scale_factor(scale_factor));
let x = self.x.into() * scale_factor;
let y = self.y.into() * scale_factor;
PhysicalPosition::new(x, y).cast()
}
@@ -243,16 +243,16 @@ impl<P: Pixel> PhysicalPosition<P> {
#[inline]
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
logical: T,
dpi_factor: f64,
scale_factor: f64,
) -> Self {
logical.into().to_physical(dpi_factor)
logical.into().to_physical(scale_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<X> {
assert!(validate_scale_factor(dpi_factor));
let x = self.x.into() / dpi_factor;
let y = self.y.into() / dpi_factor;
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalPosition<X> {
assert!(validate_scale_factor(scale_factor));
let x = self.x.into() / scale_factor;
let y = self.y.into() / scale_factor;
LogicalPosition::new(x, y).cast()
}
@@ -306,15 +306,18 @@ impl<P> LogicalSize<P> {
impl<P: Pixel> LogicalSize<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(physical: T, dpi_factor: f64) -> Self {
physical.into().to_logical(dpi_factor)
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(
physical: T,
scale_factor: f64,
) -> Self {
physical.into().to_logical(scale_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<X> {
assert!(validate_scale_factor(dpi_factor));
let width = self.width.into() * dpi_factor;
let height = self.height.into() * dpi_factor;
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalSize<X> {
assert!(validate_scale_factor(scale_factor));
let width = self.width.into() * scale_factor;
let height = self.height.into() * scale_factor;
PhysicalSize::new(width, height).cast()
}
@@ -368,15 +371,15 @@ impl<P> PhysicalSize<P> {
impl<P: Pixel> PhysicalSize<P> {
#[inline]
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, dpi_factor: f64) -> Self {
logical.into().to_physical(dpi_factor)
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self {
logical.into().to_physical(scale_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalSize<X> {
assert!(validate_scale_factor(dpi_factor));
let width = self.width.into() / dpi_factor;
let height = self.height.into() / dpi_factor;
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalSize<X> {
assert!(validate_scale_factor(scale_factor));
let width = self.width.into() / scale_factor;
let height = self.height.into() / scale_factor;
LogicalSize::new(width, height).cast()
}
@@ -426,17 +429,17 @@ impl Size {
size.into()
}
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalSize<P> {
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> {
match *self {
Size::Physical(size) => size.to_logical(dpi_factor),
Size::Physical(size) => size.to_logical(scale_factor),
Size::Logical(size) => size.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<P> {
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> {
match *self {
Size::Physical(size) => size.cast(),
Size::Logical(size) => size.to_physical(dpi_factor),
Size::Logical(size) => size.to_physical(scale_factor),
}
}
}
@@ -468,17 +471,17 @@ impl Position {
position.into()
}
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<P> {
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
match *self {
Position::Physical(position) => position.to_logical(dpi_factor),
Position::Physical(position) => position.to_logical(scale_factor),
Position::Logical(position) => position.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<P> {
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> {
match *self {
Position::Physical(position) => position.cast(),
Position::Logical(position) => position.to_physical(dpi_factor),
Position::Logical(position) => position.to_physical(scale_factor),
}
}
}

View File

@@ -139,7 +139,7 @@ impl<'a, T> Event<'a, T> {
WindowEvent { window_id, event } => event
.to_static()
.map(|event| WindowEvent { window_id, event }),
UserEvent(_) => None,
UserEvent(event) => Some(UserEvent(event)),
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
NewEvents(cause) => Some(NewEvents(cause)),
MainEventsCleared => Some(MainEventsCleared),
@@ -185,7 +185,7 @@ pub enum WindowEvent<'a> {
Resized(PhysicalSize<u32>),
/// The position of the window has changed. Contains the window's new position.
Moved(PhysicalPosition<u32>),
Moved(PhysicalPosition<i32>),
/// The window has been requested to close.
CloseRequested,
@@ -235,6 +235,13 @@ pub enum WindowEvent<'a> {
is_synthetic: bool,
},
/// The keyboard modifiers have changed.
///
/// Platform-specific behavior:
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
/// issue, and it should get fixed - but it's the current state of the API.
ModifiersChanged(ModifiersState),
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
@@ -242,8 +249,8 @@ pub enum WindowEvent<'a> {
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
position: PhysicalPosition<i32>,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
position: PhysicalPosition<f64>,
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -258,7 +265,7 @@ pub enum WindowEvent<'a> {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -267,7 +274,7 @@ pub enum WindowEvent<'a> {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
@@ -341,6 +348,7 @@ impl<'a> WindowEvent<'a> {
input,
is_synthetic,
}),
ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)),
#[allow(deprecated)]
CursorMoved {
device_id,
@@ -464,16 +472,6 @@ pub enum DeviceEvent {
Key(KeyboardInput),
/// The keyboard modifiers have changed.
///
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
///
/// Platform-specific behavior:
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
/// issue, and it should get fixed - but it's the current state of the API.
ModifiersChanged(ModifiersState),
Text {
codepoint: char,
},
@@ -502,7 +500,7 @@ pub struct KeyboardInput {
///
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
pub modifiers: ModifiersState,
}

View File

@@ -215,14 +215,10 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl<T: fmt::Debug> fmt::Display for EventLoopClosed<T> {
impl<T> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", error::Error::description(self))
f.write_str("Tried to wake up a closed `EventLoop`")
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {
fn description(&self) -> &str {
"Tried to wake up a closed `EventLoop`"
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}

View File

@@ -1,4 +1,5 @@
use std::{error::Error, fmt, mem};
use crate::platform_impl::PlatformIcon;
use std::{error::Error, fmt, io, mem};
#[repr(C)]
#[derive(Debug)]
@@ -11,7 +12,7 @@ pub(crate) struct Pixel {
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug)]
/// An error produced when using `Icon::from_rgba` with invalid arguments.
pub enum BadIcon {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
@@ -25,72 +26,110 @@ pub enum BadIcon {
width_x_height: usize,
pixel_count: usize,
},
/// Produced when underlying OS functionality failed to create the icon
OsError(io::Error),
}
impl fmt::Display for BadIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
match self {
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
byte_count,
),
&BadIcon::DimensionsVsPixelCount {
BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => format!(
} => write!(f,
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
width, height, pixel_count, width_x_height,
),
};
write!(f, "{}", msg)
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
}
}
}
impl Error for BadIcon {
fn description(&self) -> &str {
"A valid icon cannot be created from these arguments"
}
fn cause(&self) -> Option<&dyn Error> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
/// An icon used for the window titlebar, taskbar, etc.
pub struct Icon {
pub(crate) struct RgbaIcon {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u32,
pub(crate) height: u32,
}
/// For platforms which don't have window icons (e.g. web)
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct NoIcon;
#[allow(dead_code)] // These are not used on every platform
mod constructors {
use super::*;
impl RgbaIcon {
/// Creates an `Icon` from 32bpp RGBA data.
///
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
})
} else {
Ok(RgbaIcon {
rgba,
width,
height,
})
}
}
}
impl NoIcon {
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
// Create the rgba icon anyway to validate the input
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
Ok(NoIcon)
}
}
}
/// An icon used for the window titlebar, taskbar, etc.
#[derive(Clone)]
pub struct Icon {
pub(crate) inner: PlatformIcon,
}
impl fmt::Debug for Icon {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&self.inner, formatter)
}
}
impl Icon {
/// Creates an `Icon` from 32bpp RGBA data.
///
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
})
} else {
Ok(Icon {
rgba,
width,
height,
})
}
Ok(Icon {
inner: PlatformIcon::from_rgba(rgba, width, height)?,
})
}
}

View File

@@ -123,6 +123,7 @@
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
#[cfg(feature = "serde")]

View File

@@ -4,6 +4,7 @@ use std::os::raw::c_void;
use crate::{
dpi::LogicalSize,
event_loop::EventLoopWindowTarget,
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
@@ -209,3 +210,17 @@ impl MonitorHandleExtMacOS for MonitorHandle {
self.inner.ns_screen().map(|s| s as *mut c_void)
}
}
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self);
}
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
}
}

View File

@@ -2,7 +2,7 @@
use std::{os::raw, ptr, sync::Arc};
use smithay_client_toolkit::window::{ButtonState, Theme};
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
use crate::{
dpi::Size,
@@ -23,74 +23,6 @@ pub use crate::platform_impl::x11;
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// Theme for wayland client side decorations
///
/// Colors must be in ARGB8888 format
pub struct WaylandTheme {
/// Primary color when the window is focused
pub primary_active: [u8; 4],
/// Primary color when the window is unfocused
pub primary_inactive: [u8; 4],
/// Secondary color when the window is focused
pub secondary_active: [u8; 4],
/// Secondary color when the window is unfocused
pub secondary_inactive: [u8; 4],
/// Close button color when hovered over
pub close_button_hovered: [u8; 4],
/// Close button color
pub close_button: [u8; 4],
/// Close button color when hovered over
pub maximize_button_hovered: [u8; 4],
/// Maximize button color
pub maximize_button: [u8; 4],
/// Minimize button color when hovered over
pub minimize_button_hovered: [u8; 4],
/// Minimize button color
pub minimize_button: [u8; 4],
}
struct WaylandThemeObject(WaylandTheme);
impl Theme for WaylandThemeObject {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.primary_active
} else {
self.0.primary_inactive
}
}
// Used for division line
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
if active {
self.0.secondary_active
} else {
self.0.secondary_inactive
}
}
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.close_button_hovered,
_ => self.0.close_button,
}
}
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.maximize_button_hovered,
_ => self.0.maximize_button,
}
}
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
match state {
ButtonState::Hovered => self.0.minimize_button_hovered,
_ => self.0.minimize_button,
}
}
}
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the `EventLoopWindowTarget` uses Wayland.
@@ -275,7 +207,7 @@ pub trait WindowExtUnix {
fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Sets the color theme of the client side window decorations on wayland
fn set_wayland_theme(&self, theme: WaylandTheme);
fn set_wayland_theme<T: Theme>(&self, theme: T);
/// Check if the window is ready for drawing
///
@@ -353,9 +285,9 @@ impl WindowExtUnix for Window {
}
#[inline]
fn set_wayland_theme(&self, theme: WaylandTheme) {
fn set_wayland_theme<T: Theme>(&self, theme: T) {
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
_ => {}
}
}
@@ -461,3 +393,97 @@ impl MonitorHandleExtUnix for MonitorHandle {
self.inner.native_identifier()
}
}
/// Wrapper for implementing SCTK's theme trait.
struct WaylandTheme<T: Theme>(T);
pub trait Theme: Send + 'static {
/// Primary color of the scheme.
fn primary_color(&self, window_active: bool) -> [u8; 4];
/// Secondary color of the scheme.
fn secondary_color(&self, window_active: bool) -> [u8; 4];
/// Color for the close button.
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the close button, defaults to the secondary color.
#[allow(unused_variables)]
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the maximize button.
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the maximize button, defaults to the secondary color.
#[allow(unused_variables)]
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
/// Background color for the minimize button.
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
/// Icon color for the minimize button, defaults to the secondary color.
#[allow(unused_variables)]
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
self.secondary_color(true)
}
}
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
fn get_primary_color(&self, active: bool) -> [u8; 4] {
self.0.primary_color(active)
}
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
self.0.secondary_color(active)
}
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.close_button_color(ButtonState::from_sctk(status))
}
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.close_button_icon_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.maximize_button_color(ButtonState::from_sctk(status))
}
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.maximize_button_icon_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0.minimize_button_color(ButtonState::from_sctk(status))
}
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
self.0
.minimize_button_icon_color(ButtonState::from_sctk(status))
}
}
pub enum ButtonState {
/// Button is being hovered over by pointer.
Hovered,
/// Button is not being hovered over by pointer.
Idle,
/// Button is disabled.
Disabled,
}
impl ButtonState {
fn from_sctk(button_state: SCTKButtonState) -> Self {
match button_state {
SCTKButtonState::Hovered => Self::Hovered,
SCTKButtonState::Idle => Self::Idle,
SCTKButtonState::Disabled => Self::Disabled,
}
}
}

View File

@@ -3,7 +3,10 @@
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
//! Window.
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
//! your own canvas.
use crate::window::WindowBuilder;
#[cfg(feature = "stdweb")]
use stdweb::web::html_element::CanvasElement;
@@ -11,6 +14,9 @@ use stdweb::web::html_element::CanvasElement;
#[cfg(feature = "stdweb")]
pub trait WindowExtStdweb {
fn canvas(&self) -> CanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "web-sys")]
@@ -19,4 +25,35 @@ use web_sys::HtmlCanvasElement;
#[cfg(feature = "web-sys")]
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "stdweb")]
pub trait WindowBuilderExtStdweb {
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
}
#[cfg(feature = "stdweb")]
impl WindowBuilderExtStdweb for WindowBuilder {
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self
}
}
#[cfg(feature = "web-sys")]
pub trait WindowBuilderExtWebSys {
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
}
#[cfg(feature = "web-sys")]
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self
}
}

View File

@@ -1,16 +1,19 @@
#![cfg(target_os = "windows")]
use std::os::raw::c_void;
use std::path::Path;
use libc;
use winapi::shared::minwindef::WORD;
use winapi::shared::windef::HWND;
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event_loop::EventLoop,
monitor::MonitorHandle,
platform_impl::EventLoop as WindowsEventLoop,
window::{Icon, Window, WindowBuilder},
platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
window::{BadIcon, Icon, Window, WindowBuilder},
};
/// Additional methods on `EventLoop` that are specific to Windows.
@@ -171,3 +174,40 @@ impl DeviceIdExtWindows for DeviceId {
self.0.persistent_identifier()
}
}
/// Additional methods on `Icon` that are specific to Windows.
pub trait IconExtWindows: Sized {
/// Create an icon from a file path.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
-> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
fn from_path<P: AsRef<Path>>(
path: P,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon })
}
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}
}

View File

@@ -22,6 +22,8 @@ use crate::{
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
use CreationError::OsError;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub type OsError = std::io::Error;
pub struct EventLoop {
@@ -61,10 +63,10 @@ impl EventLoop {
while let Ok(event) = self.event_rx.try_recv() {
let e = match event {
android_glue::Event::EventMotion(motion) => {
let dpi_factor = MonitorHandle.scale_factor();
let scale_factor = MonitorHandle.scale_factor();
let location = LogicalPosition::from_physical(
(motion.x as f64, motion.y as f64),
dpi_factor,
scale_factor,
);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
@@ -102,9 +104,9 @@ impl EventLoop {
if native_window.is_null() {
None
} else {
let dpi_factor = MonitorHandle.scale_factor();
let scale_factor = MonitorHandle.scale_factor();
let physical_size = MonitorHandle.size();
let size = LogicalSize::from_physical(physical_size, dpi_factor);
let size = LogicalSize::from_physical(physical_size, scale_factor);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
event: WindowEvent::Resized(size),
@@ -319,9 +321,9 @@ impl Window {
if self.native_window.is_null() {
None
} else {
let dpi_factor = self.scale_factor();
let scale_factor = self.scale_factor();
let physical_size = self.current_monitor().size();
Some(LogicalSize::from_physical(physical_size, dpi_factor))
Some(LogicalSize::from_physical(physical_size, scale_factor))
}
}

View File

@@ -215,10 +215,10 @@ fn setup_control_flow_observers() {
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.

View File

@@ -83,6 +83,8 @@ pub use self::{
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {
uiscreen: ffi::id,

View File

@@ -127,12 +127,12 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let dpi_factor: CGFloat = msg_send![screen, scale];
let scale_factor: CGFloat = msg_send![screen, scale];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
}
.to_physical(dpi_factor.into());
.to_physical(scale_factor.into());
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
@@ -162,15 +162,15 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
// content scale factor to a device-specific default value", so we can't use the
// parameter here. We can query the actual factor using the getter
let dpi_factor: CGFloat = msg_send![object, contentScaleFactor];
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
assert!(
!dpi_factor.is_nan()
&& dpi_factor.is_finite()
&& dpi_factor.is_sign_positive()
&& dpi_factor > 0.0,
!scale_factor.is_nan()
&& scale_factor.is_finite()
&& scale_factor.is_sign_positive()
&& scale_factor > 0.0,
"invalid scale_factor set on UIView",
);
let scale_factor: f64 = dpi_factor.into();
let scale_factor: f64 = scale_factor.into();
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];

View File

@@ -83,8 +83,8 @@ impl Inner {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
}
@@ -95,15 +95,15 @@ impl Inner {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
}
pub fn set_outer_position(&self, physical_position: Position) {
unsafe {
let dpi_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(dpi_factor);
let scale_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
@@ -119,25 +119,25 @@ impl Inner {
pub fn inner_size(&self) -> PhysicalSize<u32> {
unsafe {
let dpi_factor = self.scale_factor();
let scale_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(dpi_factor)
size.to_physical(scale_factor)
}
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
unsafe {
let dpi_factor = self.scale_factor();
let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(dpi_factor)
size.to_physical(scale_factor)
}
}
@@ -355,8 +355,8 @@ impl Window {
let frame = match window_attributes.inner_size {
Some(dim) => {
let dpi_factor = msg_send![screen, scale];
let size = dim.to_logical::<f64>(dpi_factor);
let scale_factor = msg_send![screen, scale];
let size = dim.to_logical::<f64>(scale_factor);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
@@ -400,8 +400,8 @@ impl Window {
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let dpi_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor: f64 = dpi_factor.into();
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor: f64 = scale_factor.into();
if scale_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];

View File

@@ -18,6 +18,8 @@ use crate::{
window::{CursorIcon, Fullscreen, WindowAttributes},
};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub mod wayland;
pub mod x11;
@@ -584,13 +586,12 @@ impl<T: 'static> EventLoop<T> {
}
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
X11_BACKEND
.lock()
.as_ref()
.map(Arc::clone)
.map(x11::EventLoop::new)
.map(EventLoop::X)
.map_err(|err| err.clone())
let xconn = match X11_BACKEND.lock().as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(err.clone()),
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
}
#[inline]

View File

@@ -39,7 +39,10 @@ use crate::{
window::{CursorIcon, WindowId as RootWindowId},
};
use super::{window::WindowStore, DeviceId, WindowId};
use super::{
window::{DecorationsAction, WindowStore},
DeviceId, WindowId,
};
use smithay_client_toolkit::{
output::OutputMgr,
@@ -90,6 +93,7 @@ pub struct CursorManager {
locked_pointers: Vec<ZwpLockedPointerV1>,
cursor_visible: bool,
current_cursor: CursorIcon,
scale_factor: u32,
}
impl CursorManager {
@@ -101,6 +105,7 @@ impl CursorManager {
locked_pointers: Vec::new(),
cursor_visible: true,
current_cursor: CursorIcon::default(),
scale_factor: 1,
}
}
@@ -145,6 +150,11 @@ impl CursorManager {
}
}
pub fn update_scale_factor(&mut self, scale: u32) {
self.scale_factor = scale;
self.reload_cursor_style();
}
fn set_cursor_icon_impl(&mut self, cursor: CursorIcon) {
let cursor = match cursor {
CursorIcon::Alias => "link",
@@ -193,7 +203,7 @@ impl CursorManager {
for pointer in self.pointers.iter() {
// Ignore erros, since we don't want to fail hard in case we can't find a proper cursor
// in a given theme.
let _ = pointer.set_cursor(cursor, None);
let _ = pointer.set_cursor_with_scale(cursor, self.scale_factor, None);
}
}
@@ -706,6 +716,13 @@ impl<T> EventLoop<T> {
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid));
if let Some(frame) = window.frame {
if let Some((w, h)) = window.newsize {
// Update decorations state
match window.decorations_action {
Some(DecorationsAction::Hide) => frame.set_decorate(false),
Some(DecorationsAction::Show) => frame.set_decorate(true),
None => (),
}
// mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case
// it overlaps mutter's `bounding box`, so we can't avoid this resize call,
// which calls `set_geometry` under the hood, for now.
@@ -728,6 +745,13 @@ impl<T> EventLoop<T> {
}
if let Some(dpi) = window.new_dpi {
// Update cursor scale factor
{
self.cursor_manager
.lock()
.unwrap()
.update_scale_factor(dpi as u32);
};
let dpi = dpi as f64;
let logical_size = LogicalSize::<f64>::from(*window.size);
let mut new_inner_size = logical_size.to_physical(dpi);
@@ -742,6 +766,8 @@ impl<T> EventLoop<T> {
let (w, h) = new_inner_size.to_logical::<u32>(dpi).into();
frame.resize(w, h);
// Refresh frame to rescale decorations
frame.refresh();
*window.size = (w, h);
}
}

View File

@@ -8,9 +8,7 @@ use smithay_client_toolkit::{
reexports::client::protocol::{wl_keyboard, wl_seat},
};
use crate::event::{
DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
};
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
pub fn init_keyboard(
seat: &wl_seat::WlSeat,
@@ -33,9 +31,24 @@ pub fn init_keyboard(
let wid = make_wid(&surface);
my_sink.send_window_event(WindowEvent::Focused(true), wid);
*target.lock().unwrap() = Some(wid);
let modifiers = *modifiers_tracker.lock().unwrap();
if !modifiers.is_empty() {
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
}
}
KbEvent::Leave { surface, .. } => {
let wid = make_wid(&surface);
let modifiers = *modifiers_tracker.lock().unwrap();
if !modifiers.is_empty() {
my_sink.send_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty()),
wid,
);
}
my_sink.send_window_event(WindowEvent::Focused(false), wid);
*target.lock().unwrap() = None;
}
@@ -88,7 +101,9 @@ pub fn init_keyboard(
*modifiers_tracker.lock().unwrap() = modifiers;
my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId);
if let Some(wid) = *target.lock().unwrap() {
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
}
}
}
},

View File

@@ -1,5 +1,6 @@
use std::sync::{Arc, Mutex};
use crate::dpi::LogicalPosition;
use crate::event::{
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
@@ -7,10 +8,13 @@ use crate::event::{
use super::{
event_loop::{CursorManager, EventsSink},
make_wid,
window::WindowStore,
DeviceId,
};
use smithay_client_toolkit::surface;
use smithay_client_toolkit::reexports::client::protocol::{
wl_pointer::{self, Event as PtrEvent, WlPointer},
wl_seat,
@@ -36,6 +40,7 @@ pub fn implement_pointer(
cursor_manager: Arc<Mutex<CursorManager>>,
) -> WlPointer {
seat.get_pointer(|pointer| {
// Currently focused winit surface
let mut mouse_focus = None;
let mut axis_buffer = None;
let mut axis_discrete_buffer = None;
@@ -53,8 +58,10 @@ pub fn implement_pointer(
..
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
mouse_focus = Some(wid);
let scale_factor = surface::get_dpi_factor(&surface) as f64;
mouse_focus = Some(surface);
// Reload cursor style only when we enter winit's surface. Calling
// this function every time on `PtrEvent::Enter` could interfere with
@@ -70,12 +77,16 @@ pub fn implement_pointer(
},
wid,
);
let position = LogicalPosition::new(surface_x, surface_y)
.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x, surface_y).into(),
position,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
@@ -101,13 +112,19 @@ pub fn implement_pointer(
surface_y,
..
} => {
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
let scale_factor = surface::get_dpi_factor(&surface) as f64;
let position = LogicalPosition::new(surface_x, surface_y)
.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::CursorMoved {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
position: (surface_x, surface_y).into(),
position,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
@@ -115,7 +132,7 @@ pub fn implement_pointer(
}
}
PtrEvent::Button { button, state, .. } => {
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let state = match state {
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
wl_pointer::ButtonState::Released => ElementState::Released,
@@ -137,12 +154,13 @@ pub fn implement_pointer(
button,
modifiers: modifiers_tracker.lock().unwrap().clone(),
},
wid,
make_wid(surface),
);
}
}
PtrEvent::Axis { axis, value, .. } => {
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// old seat compatibility
@@ -184,7 +202,8 @@ pub fn implement_pointer(
PtrEvent::Frame => {
let axis_buffer = axis_buffer.take();
let axis_discrete_buffer = axis_discrete_buffer.take();
if let Some(wid) = mouse_focus {
if let Some(surface) = mouse_focus.as_ref() {
let wid = make_wid(surface);
if let Some((x, y)) = axis_discrete_buffer {
sink.send_window_event(
WindowEvent::MouseWheel {

View File

@@ -1,17 +1,22 @@
use std::sync::{Arc, Mutex};
use crate::dpi::LogicalPosition;
use crate::event::{TouchPhase, WindowEvent};
use super::{event_loop::EventsSink, window::WindowStore, DeviceId, WindowId};
use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId};
use smithay_client_toolkit::surface;
use smithay_client_toolkit::reexports::client::protocol::{
wl_seat,
wl_surface::WlSurface,
wl_touch::{Event as TouchEvent, WlTouch},
};
// location is in logical coordinates.
struct TouchPoint {
wid: WindowId,
location: (f64, f64),
surface: WlSurface,
position: LogicalPosition<f64>,
id: i32,
}
@@ -31,21 +36,24 @@ pub(crate) fn implement_touch(
} => {
let wid = store.find_wid(&surface);
if let Some(wid) = wid {
let scale_factor = surface::get_dpi_factor(&surface) as f64;
let position = LogicalPosition::new(x, y);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Started,
location: (x, y).into(),
location: position.to_physical(scale_factor),
force: None, // TODO
id: id as u64,
}),
wid,
);
pending_ids.push(TouchPoint {
wid,
location: (x, y),
surface,
position,
id,
});
}
@@ -54,52 +62,63 @@ pub(crate) fn implement_touch(
let idx = pending_ids.iter().position(|p| p.id == id);
if let Some(idx) = idx {
let pt = pending_ids.remove(idx);
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Ended,
location: pt.location.into(),
location,
force: None, // TODO
id: id as u64,
}),
pt.wid,
make_wid(&pt.surface),
);
}
}
TouchEvent::Motion { id, x, y, .. } => {
let pt = pending_ids.iter_mut().find(|p| p.id == id);
if let Some(pt) = pt {
pt.location = (x, y);
pt.position = LogicalPosition::new(x, y);
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Moved,
location: (x, y).into(),
location,
force: None, // TODO
id: id as u64,
}),
pt.wid,
make_wid(&pt.surface),
);
}
}
TouchEvent::Frame => (),
TouchEvent::Cancel => {
for pt in pending_ids.drain(..) {
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
let location = pt.position.to_physical(scale_factor);
sink.send_window_event(
WindowEvent::Touch(crate::event::Touch {
device_id: crate::event::DeviceId(
crate::platform_impl::DeviceId::Wayland(DeviceId),
),
phase: TouchPhase::Cancelled,
location: pt.location.into(),
location,
force: None, // TODO
id: pt.id as u64,
}),
pt.wid,
make_wid(&pt.surface),
);
}
}

View File

@@ -41,6 +41,13 @@ pub struct Window {
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
decorated: Arc<Mutex<bool>>,
}
#[derive(Clone, Copy, Debug)]
pub enum DecorationsAction {
Hide,
Show,
}
impl Window {
@@ -69,6 +76,9 @@ impl Window {
let window_store = evlp.store.clone();
let decorated = Arc::new(Mutex::new(attributes.decorations));
let pending_decorations_action = Arc::new(Mutex::new(None));
let my_surface = surface.clone();
let mut frame = SWindow::<ConceptFrame>::init_from_env(
&evlp.env,
@@ -83,7 +93,23 @@ impl Window {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
window.newsize = new_size;
*(window.need_refresh.lock().unwrap()) = true;
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
{
// Get whether we're in fullscreen
let mut fullscreen = window.fullscreen.lock().unwrap();
// Fullscreen state was changed, so update decorations
if *fullscreen != is_fullscreen {
let decorated = { *window.decorated.lock().unwrap() };
if decorated {
*window.pending_decorations_action.lock().unwrap() =
if is_fullscreen {
Some(DecorationsAction::Hide)
} else {
Some(DecorationsAction::Show)
};
}
}
*fullscreen = is_fullscreen;
}
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
@@ -174,6 +200,8 @@ impl Window {
frame: Arc::downgrade(&frame),
current_dpi: 1,
new_dpi: None,
decorated: decorated.clone(),
pending_decorations_action: pending_decorations_action.clone(),
});
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
@@ -189,6 +217,7 @@ impl Window {
cursor_manager,
fullscreen,
cursor_grab_changed,
decorated,
})
}
@@ -277,6 +306,7 @@ impl Window {
}
pub fn set_decorations(&self, decorate: bool) {
*(self.decorated.lock().unwrap()) = decorate;
self.frame.lock().unwrap().set_decorate(decorate);
*(self.need_frame_refresh.lock().unwrap()) = true;
}
@@ -409,6 +439,8 @@ struct InternalWindow {
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
current_dpi: i32,
new_dpi: Option<i32>,
decorated: Arc<Mutex<bool>>,
pending_decorations_action: Arc<Mutex<Option<DecorationsAction>>>,
}
pub struct WindowStore {
@@ -425,6 +457,7 @@ pub struct WindowStoreForEach<'a> {
pub surface: &'a wl_surface::WlSurface,
pub wid: WindowId,
pub frame: Option<&'a mut SWindow<ConceptFrame>>,
pub decorations_action: Option<DecorationsAction>,
}
impl WindowStore {
@@ -481,9 +514,11 @@ impl WindowStore {
for window in &mut self.windows {
let opt_arc = window.frame.upgrade();
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
let mut size = { *window.size.lock().unwrap() };
let decorations_action = { window.pending_decorations_action.lock().unwrap().take() };
f(WindowStoreForEach {
newsize: window.newsize.take(),
size: &mut *(window.size.lock().unwrap()),
size: &mut size,
prev_dpi: window.current_dpi,
new_dpi: window.new_dpi,
closed: window.closed,
@@ -491,7 +526,9 @@ impl WindowStore {
surface: &window.surface,
wid: make_wid(&window.surface),
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
decorations_action,
});
*window.size.lock().unwrap() = size;
if let Some(dpi) = window.new_dpi.take() {
window.current_dpi = dpi;
}

View File

@@ -2,6 +2,8 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
use parking_lot::MutexGuard;
use super::{
events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd,
DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
@@ -30,6 +32,8 @@ pub(super) struct EventProcessor<T: 'static> {
// Number of touch events currently in progress
pub(super) num_touch: u32,
pub(super) first_touch: Option<u64>,
// Currently focused window belonging to this process
pub(super) active_window: Option<ffi::Window>,
}
impl<T: 'static> EventProcessor<T> {
@@ -134,11 +138,12 @@ impl<T: 'static> EventProcessor<T> {
if let Some(modifiers) =
self.device_mod_state.update_state(&state, modifier)
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged(modifiers),
});
if let Some(window_id) = self.active_window {
callback(Event::WindowEvent {
window_id: mkwid(window_id),
event: WindowEvent::ModifiersChanged(modifiers),
});
}
}
}
}
@@ -384,9 +389,12 @@ impl<T: 'static> EventProcessor<T> {
.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
shared_state_lock.position = Some(outer);
if moved {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(outer.into()),
// Temporarily unlock shared state to prevent deadlock
MutexGuard::unlocked(&mut shared_state_lock, || {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(outer.into()),
});
});
}
outer
@@ -426,12 +434,15 @@ impl<T: 'static> EventProcessor<T> {
let old_inner_size = PhysicalSize::new(width, height);
let mut new_inner_size = PhysicalSize::new(new_width, new_height);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor,
new_inner_size: &mut new_inner_size,
},
// Temporarily unlock shared state to prevent deadlock
MutexGuard::unlocked(&mut shared_state_lock, || {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor,
new_inner_size: &mut new_inner_size,
},
});
});
if new_inner_size != old_inner_size {
@@ -461,6 +472,9 @@ impl<T: 'static> EventProcessor<T> {
}
if resized {
// Drop the shared state lock to prevent deadlock
drop(shared_state_lock);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(new_inner_size.into()),
@@ -712,8 +726,7 @@ impl<T: 'static> EventProcessor<T> {
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
});
if cursor_moved == Some(true) {
let position =
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
callback(Event::WindowEvent {
window_id,
@@ -819,8 +832,7 @@ impl<T: 'static> EventProcessor<T> {
event: CursorEntered { device_id },
});
let position =
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
// The mods field on this event isn't actually populated, so query the
// pointer device. In the future, we can likely remove this round-trip by
@@ -863,45 +875,58 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
wt.ime
.borrow_mut()
.focus(xev.event)
.expect("Failed to focus input context");
callback(Event::WindowEvent {
window_id,
event: Focused(true),
});
let modifiers = ModifiersState::from_x11(&xev.mods);
update_modifiers!(modifiers, None);
self.device_mod_state.update_state(&modifiers, None);
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
.devices
.borrow()
.get(&DeviceId(xev.deviceid))
.map(|device| device.attachment)
.unwrap_or(2);
if self.active_window != Some(xev.event) {
self.active_window = Some(xev.event);
let position =
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
let window_id = mkwid(xev.event);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers,
},
});
callback(Event::WindowEvent {
window_id,
event: Focused(true),
});
// Issue key press events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback);
if !modifiers.is_empty() {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(modifiers),
});
}
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
.devices
.borrow()
.get(&DeviceId(xev.deviceid))
.map(|device| device.attachment)
.unwrap_or(2);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
position,
modifiers,
},
});
// Issue key press events for all pressed keys
self.handle_pressed_keys(
window_id,
ElementState::Pressed,
&mut callback,
);
}
}
ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
@@ -913,15 +938,26 @@ impl<T: 'static> EventProcessor<T> {
.unfocus(xev.event)
.expect("Failed to unfocus input context");
let window_id = mkwid(xev.event);
if self.active_window.take() == Some(xev.event) {
let window_id = mkwid(xev.event);
// Issue key release events for all pressed keys
self.handle_pressed_keys(window_id, ElementState::Released, &mut callback);
// Issue key release events for all pressed keys
self.handle_pressed_keys(
window_id,
ElementState::Released,
&mut callback,
);
callback(Event::WindowEvent {
window_id,
event: Focused(false),
})
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(ModifiersState::empty()),
});
callback(Event::WindowEvent {
window_id,
event: Focused(false),
})
}
}
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
@@ -1076,10 +1112,12 @@ impl<T: 'static> EventProcessor<T> {
let new_modifiers = self.device_mod_state.modifiers();
if modifiers != new_modifiers {
callback(Event::DeviceEvent {
device_id,
event: DeviceEvent::ModifiersChanged(new_modifiers),
});
if let Some(window_id) = self.active_window {
callback(Event::WindowEvent {
window_id: mkwid(window_id),
event: WindowEvent::ModifiersChanged(new_modifiers),
});
}
}
}
}

View File

@@ -21,7 +21,8 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
// XSetLocaleModifiers returns...
// * The current locale modifiers if it's given a NULL pointer.
// * The new locale modifiers if we succeeded in setting them.
// * NULL if the locale modifiers string is malformed.
// * NULL if the locale modifiers string is malformed or if the
// current locale is not supported by Xlib.
(xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr());
let im = (xconn.xlib.XOpenIM)(

View File

@@ -29,6 +29,7 @@ use std::{
mem::{self, MaybeUninit},
ops::Deref,
os::raw::*,
ptr,
rc::Rc,
slice,
sync::{mpsc, Arc, Mutex, Weak},
@@ -105,7 +106,25 @@ impl<T: 'static> EventLoop<T> {
// Input methods will open successfully without setting the locale, but it won't be
// possible to actually commit pre-edit sequences.
unsafe {
// Remember default locale to restore it if target locale is unsupported
// by Xlib
let default_locale = setlocale(LC_CTYPE, ptr::null());
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
// Check if set locale is supported by Xlib.
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
// will fail.
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
if !locale_supported {
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
warn!(
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
CStr::from_ptr(unsupported_locale).to_string_lossy(),
CStr::from_ptr(default_locale).to_string_lossy()
);
// Restore default locale
setlocale(LC_CTYPE, default_locale);
}
}
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn));
@@ -206,6 +225,7 @@ impl<T: 'static> EventLoop<T> {
device_mod_state: Default::default(),
num_touch: 0,
first_touch: None,
active_window: None,
};
// Register for device hotplug events
@@ -395,7 +415,6 @@ impl<T: 'static> EventLoop<T> {
let mut xev = MaybeUninit::uninit();
let wt = get_xtarget(&self.target);
let mut pending_redraws = wt.pending_redraws.lock().unwrap();
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
@@ -409,7 +428,7 @@ impl<T: 'static> EventLoop<T> {
super::WindowId::X(wid),
)) = event
{
pending_redraws.insert(wid);
wt.pending_redraws.lock().unwrap().insert(wid);
} else {
callback(event, window_target, control_flow);
}
@@ -490,21 +509,9 @@ impl<'a> Deref for DeviceInfo<'a> {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
pub struct Window(Arc<UnownedWindow>);
impl Deref for Window {

View File

@@ -43,62 +43,4 @@ impl XConnection {
};
self.send_event(target_window, event_mask, event)
}
// Prepare yourself for the ultimate in unsafety!
// You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you
// to send more than one message worth of data.
pub fn send_client_msg_multi<T: Formattable>(
&self,
window: c_ulong, // The window this is "about"; not necessarily this window
target_window: c_ulong, // The window we're sending to
message_type: ffi::Atom,
event_mask: Option<c_long>,
data: &[T],
) -> Flusher<'_> {
let format = T::FORMAT;
let size_of_t = mem::size_of::<T>();
debug_assert_eq!(size_of_t, format.get_actual_size());
let mut event = ffi::XClientMessageEvent {
type_: ffi::ClientMessage,
display: self.display,
window,
message_type,
format: format as c_int,
data: ffi::ClientMessageData::new(),
// These fields are ignored by `XSendEvent`
serial: 0,
send_event: 0,
};
let t_per_payload = format.get_payload_size() / size_of_t;
assert!(t_per_payload > 0);
let payload_count = data.len() / t_per_payload;
let payload_remainder = data.len() % t_per_payload;
let payload_ptr = data.as_ptr() as *const ClientMsgPayload;
let mut payload_index = 0;
while payload_index < payload_count {
let payload = unsafe { payload_ptr.offset(payload_index as isize) };
payload_index += 1;
event.data = unsafe { mem::transmute(*payload) };
self.send_event(target_window, event_mask, &event).queue();
}
if payload_remainder > 0 {
let mut payload: ClientMsgPayload = [0; 5];
let t_payload = payload.as_mut_ptr() as *mut T;
let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) };
let invalid_t_payload = invalid_payload as *const T;
let mut t_index = 0;
while t_index < payload_remainder {
let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) };
unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() };
t_index += 1;
}
event.data = unsafe { mem::transmute(payload) };
self.send_event(target_window, event_mask, &event).queue();
}
Flusher::new(self)
}
}

View File

@@ -21,10 +21,6 @@ impl Format {
}
}
pub fn is_same_size_as<T>(&self) -> bool {
mem::size_of::<T>() == self.get_actual_size()
}
pub fn get_actual_size(&self) -> usize {
match self {
&Format::Char => mem::size_of::<c_char>(),
@@ -32,15 +28,6 @@ impl Format {
&Format::Long => mem::size_of::<c_long>(),
}
}
pub fn get_payload_size(&self) -> usize {
match self {
// Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit).
&Format::Char => mem::size_of::<c_char>() * 20,
&Format::Short => mem::size_of::<c_short>() * 10,
&Format::Long => mem::size_of::<c_long>() * 5,
}
}
}
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {

View File

@@ -1,7 +1,6 @@
use std::cmp;
use super::*;
use crate::dpi::{LogicalPosition, LogicalSize};
// Friendly neighborhood axis-aligned rectangle
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -89,16 +88,6 @@ impl FrameExtents {
pub fn from_border(border: c_ulong) -> Self {
Self::new(border, border, border, border)
}
pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents {
let logicalize = |value: c_ulong| value as f64 / factor;
LogicalFrameExtents {
left: logicalize(self.left),
right: logicalize(self.right),
top: logicalize(self.top),
bottom: logicalize(self.bottom),
}
}
}
#[derive(Debug, Clone)]
@@ -135,20 +124,6 @@ impl FrameExtentsHeuristic {
}
}
pub fn inner_pos_to_outer_logical(
&self,
mut logical: LogicalPosition<f64>,
factor: f64,
) -> LogicalPosition<f64> {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
let frame_extents = self.frame_extents.as_logical(factor);
logical.x -= frame_extents.left;
logical.y -= frame_extents.top;
}
logical
}
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
(
width.saturating_add(
@@ -163,17 +138,6 @@ impl FrameExtentsHeuristic {
),
)
}
pub fn inner_size_to_outer_logical(
&self,
mut logical: LogicalSize<f64>,
factor: f64,
) -> LogicalSize<f64> {
let frame_extents = self.frame_extents.as_logical(factor);
logical.width += frame_extents.left + frame_extents.right;
logical.height += frame_extents.top + frame_extents.bottom;
logical
}
}
impl XConnection {

View File

@@ -4,6 +4,7 @@ use std::sync::Arc;
use super::*;
#[derive(Debug)]
#[allow(dead_code)]
pub enum StateOperation {
Remove = 0, // _NET_WM_STATE_REMOVE
Add = 1, // _NET_WM_STATE_ADD
@@ -189,22 +190,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn has_flag(&self, flag: c_long) -> bool {
has_flag(self.size_hints.flags, flag)
}
fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> {
if self.has_flag(flag) {
Some((*field1 as _, *field2 as _))
} else {
None
}
}
pub fn get_size(&self) -> Option<(u32, u32)> {
self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height)
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {
@@ -216,14 +201,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_max_size(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PMaxSize,
&self.size_hints.max_width,
&self.size_hints.max_height,
)
}
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
if let Some((max_width, max_height)) = max_size {
self.size_hints.flags |= ffi::PMaxSize;
@@ -234,14 +211,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_min_size(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PMinSize,
&self.size_hints.min_width,
&self.size_hints.min_height,
)
}
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
if let Some((min_width, min_height)) = min_size {
self.size_hints.flags |= ffi::PMinSize;
@@ -252,14 +221,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PResizeInc,
&self.size_hints.width_inc,
&self.size_hints.height_inc,
)
}
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
if let Some((width_inc, height_inc)) = resize_increments {
self.size_hints.flags |= ffi::PResizeInc;
@@ -270,14 +231,6 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_base_size(&self) -> Option<(u32, u32)> {
self.getter(
ffi::PBaseSize,
&self.size_hints.base_width,
&self.size_hints.base_height,
)
}
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
if let Some((base_width, base_height)) = base_size {
self.size_hints.flags |= ffi::PBaseSize;

View File

@@ -1,5 +1,5 @@
use super::*;
use crate::window::{Icon, Pixel, PIXEL_SIZE};
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
impl Pixel {
pub fn to_packed_argb(&self) -> Cardinal {
@@ -18,13 +18,14 @@ impl Pixel {
impl Icon {
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = self.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (self.width * self.height) as usize);
let rgba_icon = &self.inner;
assert_eq!(rgba_icon.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = rgba_icon.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (rgba_icon.width * rgba_icon.height) as usize);
let mut data = Vec::with_capacity(pixel_count);
data.push(self.width as Cardinal);
data.push(self.height as Cardinal);
let pixels = self.rgba.as_ptr() as *const Pixel;
data.push(rgba_icon.width as Cardinal);
data.push(rgba_icon.height as Cardinal);
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
data.push(pixel.to_packed_argb());

View File

@@ -23,18 +23,12 @@ pub use self::{
use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
ptr,
};
use super::{ffi, XConnection, XError};
pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B {
let b_ptr = a as *const _ as *const B;
unsafe { &*b_ptr }
}
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
let wrapped = Some(value);
if *field != wrapped {
@@ -45,13 +39,6 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
}
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
bitset & flag == flag
}
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
pub struct Flusher<'a> {
xconn: &'a XConnection,

View File

@@ -26,6 +26,7 @@ impl GetPropertyError {
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
#[derive(Debug)]
#[allow(dead_code)]
pub enum PropMode {
Replace = ffi::PropModeReplace as isize,
Prepend = ffi::PropModePrepend as isize,

View File

@@ -136,23 +136,23 @@ impl UnownedWindow {
})
.unwrap_or_else(|| monitors.swap_remove(0))
};
let dpi_factor = guessed_monitor.scale_factor();
let scale_factor = guessed_monitor.scale_factor();
info!("Guessed window scale factor: {}", dpi_factor);
info!("Guessed window scale factor: {}", scale_factor);
let max_inner_size: Option<(u32, u32)> = window_attrs
.max_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor).into());
.map(|size| size.to_physical::<u32>(scale_factor).into());
let min_inner_size: Option<(u32, u32)> = window_attrs
.min_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor).into());
.map(|size| size.to_physical::<u32>(scale_factor).into());
let dimensions = {
// x11 only applies constraints when the window is actively resized
// by the user, so we have to manually apply the initial constraints
let mut dimensions: (u32, u32) = window_attrs
.inner_size
.map(|size| size.to_physical::<u32>(dpi_factor))
.map(|size| size.to_physical::<u32>(scale_factor))
.or_else(|| Some((800, 600).into()))
.map(Into::into)
.unwrap();
@@ -324,10 +324,10 @@ impl UnownedWindow {
{
let mut min_inner_size = window_attrs
.min_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor));
.map(|size| size.to_physical::<u32>(scale_factor));
let mut max_inner_size = window_attrs
.max_inner_size
.map(|size| size.to_physical::<u32>(dpi_factor));
.map(|size| size.to_physical::<u32>(scale_factor));
if !window_attrs.resizable {
if util::wm_name_is_one_of(&["Xfwm4"]) {
@@ -351,12 +351,12 @@ impl UnownedWindow {
normal_hints.set_resize_increments(
pl_attribs
.resize_increments
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
.map(|size| size.to_physical::<u32>(scale_factor).into()),
);
normal_hints.set_base_size(
pl_attribs
.base_size
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
.map(|size| size.to_physical::<u32>(scale_factor).into()),
);
xconn.set_normal_hints(window.xwindow, normal_hints).queue();
}
@@ -1053,8 +1053,8 @@ impl UnownedWindow {
#[inline]
pub fn set_inner_size(&self, size: Size) {
let dpi_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(dpi_factor).into();
let scale_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(scale_factor).into();
self.set_inner_size_physical(width, height);
}
@@ -1097,16 +1097,16 @@ impl UnownedWindow {
pub(crate) fn adjust_for_dpi(
&self,
old_dpi_factor: f64,
new_dpi_factor: f64,
old_scale_factor: f64,
new_scale_factor: f64,
width: u32,
height: u32,
shared_state: &SharedState,
) -> (u32, u32) {
let scale_factor = new_dpi_factor / old_dpi_factor;
let scale_factor = new_scale_factor / old_scale_factor;
self.update_normal_hints(|normal_hints| {
let dpi_adjuster =
|size: Size| -> (u32, u32) { size.to_physical::<u32>(new_dpi_factor).into() };
|size: Size| -> (u32, u32) { size.to_physical::<u32>(new_scale_factor).into() };
let max_size = shared_state.max_inner_size.map(&dpi_adjuster);
let min_size = shared_state.min_inner_size.map(&dpi_adjuster);
let resize_increments = shared_state.resize_increments.map(&dpi_adjuster);
@@ -1146,12 +1146,12 @@ impl UnownedWindow {
self.set_maximizable_inner(resizable).queue();
let dpi_factor = self.scale_factor();
let scale_factor = self.scale_factor();
let min_inner_size = min_size
.map(|size| size.to_physical::<u32>(dpi_factor))
.map(|size| size.to_physical::<u32>(scale_factor))
.map(Into::into);
let max_inner_size = max_size
.map(|size| size.to_physical::<u32>(dpi_factor))
.map(|size| size.to_physical::<u32>(scale_factor))
.map(Into::into);
self.update_normal_hints(|normal_hints| {
normal_hints.set_min_size(min_inner_size);

View File

@@ -111,12 +111,7 @@ pub struct XError {
pub minor_code: u8,
}
impl Error for XError {
#[inline]
fn description(&self) -> &str {
&self.description
}
}
impl Error for XError {}
impl fmt::Display for XError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
@@ -144,17 +139,18 @@ impl From<ffi::OpenError> for XNotSupported {
}
}
impl Error for XNotSupported {
#[inline]
fn description(&self) -> &str {
match *self {
impl XNotSupported {
fn description(&self) -> &'static str {
match self {
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
}
}
}
impl Error for XNotSupported {
#[inline]
fn cause(&self) -> Option<&dyn Error> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
XNotSupported::LibraryOpenError(ref err) => Some(err),
_ => None,

View File

@@ -0,0 +1,208 @@
// Normally when you run or distribute a macOS app, it's bundled: it's in one
// of those fun little folders that you have to right click "Show Package
// Contents" on, and usually contains myriad delights including, but not
// limited to, plists, icons, and of course, your beloved executable. However,
// when you use `cargo run`, your app is unbundled - it's just a lonely, bare
// executable.
//
// Apple isn't especially fond of unbundled apps, which is to say, they seem to
// barely be supported. If you move the mouse while opening a winit window from
// an unbundled app, the window will fail to activate and be in a grayed-out
// uninteractable state. Switching to another app and back is the only way to
// get the winit window into a normal state. None of this happens if the app is
// bundled, i.e. when running via Xcode.
//
// To workaround this, we just switch focus to the Dock and then switch back to
// our app. We only do this for unbundled apps, and only when they fail to
// become active on their own.
//
// This solution was derived from this Godot PR:
// https://github.com/godotengine/godot/pull/17187
// (which appears to be based on https://stackoverflow.com/a/7602677)
// The curious specialness of mouse motions is touched upon here:
// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512
//
// We omit the 2nd step of the solution used in Godot, since it appears to have
// no effect - I speculate that it's just technical debt picked up from the SO
// answer; the API used is fairly exotic, and was historically used for very
// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e.
// in previous versions of SDL:
// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322
//
// The `performSelector` delays in the Godot solution are used for sequencing,
// since refocusing the app will fail if the call is made before it finishes
// unfocusing. The delays used there are much smaller than the ones in the
// original SO answer, presumably because they found the fastest delay that
// works reliably through trial and error. Instead of using delays, we just
// handle `applicationDidResignActive`; despite the app not activating reliably,
// that still triggers when we switch focus to the Dock.
//
// The Godot solution doesn't appear to skip the hack when an unbundled app
// activates normally. Checking for this is difficult, since if you call
// `isActive` too early, it will always be `NO`. Even though we receive
// `applicationDidResignActive` when switching focus to the Dock, we never
// receive a preceding `applicationDidBecomeActive` if the app fails to
// activate normally. I wasn't able to find a proper point in time to perform
// the `isActive` check, so we instead check for the cause of the quirk: if
// any mouse motion occurs prior to us receiving `applicationDidResignActive`,
// we assume the app failed to become active.
//
// Fun fact: this issue is still present in GLFW
// (https://github.com/glfw/glfw/issues/1515)
//
// A similar issue was found in SDL, but the resolution doesn't seem to work
// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051
use super::util;
use cocoa::{
appkit::{NSApp, NSApplicationActivateIgnoringOtherApps},
base::id,
foundation::NSUInteger,
};
use objc::runtime::{Object, Sel, BOOL, NO, YES};
use std::{
os::raw::c_void,
sync::atomic::{AtomicBool, Ordering},
};
#[derive(Debug, Default)]
pub struct State {
// Indicates that the hack has either completed or been skipped.
activated: AtomicBool,
// Indicates that the mouse has moved at some point in time.
mouse_moved: AtomicBool,
// Indicates that the hack is in progress, and that we should refocus when
// the app resigns active.
needs_refocus: AtomicBool,
}
impl State {
pub fn name() -> &'static str {
"activationHackState"
}
pub fn new() -> *mut c_void {
let this = Box::new(Self::default());
Box::into_raw(this) as *mut c_void
}
pub unsafe fn free(this: *mut Self) {
Box::from_raw(this);
}
pub unsafe fn get_ptr(obj: &Object) -> *mut Self {
let this: *mut c_void = *(*obj).get_ivar(Self::name());
assert!(!this.is_null(), "`activationHackState` pointer was null");
this as *mut Self
}
pub unsafe fn set_activated(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).activated.store(value, Ordering::Release);
}
unsafe fn get_activated(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).activated.load(Ordering::Acquire)
}
pub unsafe fn set_mouse_moved(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).mouse_moved.store(value, Ordering::Release);
}
pub unsafe fn get_mouse_moved(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).mouse_moved.load(Ordering::Acquire)
}
pub unsafe fn set_needs_refocus(obj: &Object, value: bool) {
let this = Self::get_ptr(obj);
(*this).needs_refocus.store(value, Ordering::Release);
}
unsafe fn get_needs_refocus(obj: &Object) -> bool {
let this = Self::get_ptr(obj);
(*this).needs_refocus.load(Ordering::Acquire)
}
}
// This is the entry point for the hack - if the app is unbundled and a mouse
// movement occurs before the app activates, it will trigger the hack. Because
// mouse movements prior to activation are the cause of this quirk, they should
// be a reliable way to determine if the hack needs to be performed.
pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) {
trace!("Triggered `activationHackMouseMoved`");
unsafe {
if !State::get_activated(this) {
// We check if `CFBundleName` is undefined to determine if the
// app is unbundled.
if let None = util::app_name() {
info!("App detected as unbundled");
unfocus(this);
} else {
info!("App detected as bundled");
}
}
}
trace!("Completed `activationHackMouseMoved`");
}
// Switch focus to the dock.
unsafe fn unfocus(this: &Object) {
// We only perform the hack if the app failed to activate, since otherwise,
// there'd be a gross (but fast) flicker as it unfocused and then refocused.
// However, we only enter this function if we detect mouse movement prior
// to activation, so this should always be `NO`.
//
// Note that this check isn't necessarily reliable in detecting a violation
// of the invariant above, since it's not guaranteed that activation will
// resolve before this point. In other words, it can spuriously return `NO`.
// This is also why the mouse motion approach was chosen, since it's not
// obvious how to sequence this check - if someone knows how to, then that
// would almost surely be a cleaner approach.
let active: BOOL = msg_send![NSApp(), isActive];
if active == YES {
error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!");
} else {
info!("Performing unbundled app activation hack");
let dock_bundle_id = util::ns_string_id_ref("com.apple.dock");
let dock_array: id = msg_send![
class!(NSRunningApplication),
runningApplicationsWithBundleIdentifier: *dock_bundle_id
];
let dock_array_len: NSUInteger = msg_send![dock_array, count];
if dock_array_len == 0 {
error!("The Dock doesn't seem to be running, so switching focus to it is impossible");
} else {
State::set_needs_refocus(this, true);
let dock: id = msg_send![dock_array, objectAtIndex: 0];
// This will trigger `applicationDidResignActive`, which will in
// turn call `refocus`.
let status: BOOL = msg_send![
dock,
activateWithOptions: NSApplicationActivateIgnoringOtherApps
];
if status == NO {
error!("Failed to switch focus to Dock");
}
}
}
}
// Switch focus back to our app, causing the user to rejoice!
pub unsafe fn refocus(this: &Object) {
if State::get_needs_refocus(this) {
State::set_needs_refocus(this, false);
let app: id = msg_send![class!(NSRunningApplication), currentApplication];
// Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The
// nuanced difference isn't clear to me, but hey, I tried.
let success: BOOL = msg_send![
app,
activateWithOptions: NSApplicationActivateIgnoringOtherApps
];
if success == NO {
error!("Failed to refocus app");
}
}
}

View File

@@ -2,17 +2,15 @@ use std::collections::VecDeque;
use cocoa::{
appkit::{self, NSEvent},
base::id,
base::{id, nil},
};
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel},
};
use crate::{
event::{DeviceEvent, ElementState, Event},
platform_impl::platform::{app_state::AppState, event::EventWrapper, util, DEVICE_ID},
};
use super::{activation_hack, app_state::AppState, event::EventWrapper, util, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event};
pub struct AppClass(pub *const Class);
unsafe impl Send for AppClass {}
@@ -51,14 +49,14 @@ extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
let key_window: id = msg_send![this, keyWindow];
let _: () = msg_send![key_window, sendEvent: event];
} else {
maybe_dispatch_device_event(event);
maybe_dispatch_device_event(this, event);
let superclass = util::superclass(this);
let _: () = msg_send![super(this, superclass), sendEvent: event];
}
}
}
unsafe fn maybe_dispatch_device_event(event: id) {
unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
let event_type = event.eventType();
match event_type {
appkit::NSMouseMoved
@@ -100,6 +98,21 @@ unsafe fn maybe_dispatch_device_event(event: id) {
}
AppState::queue_events(events);
// Notify the delegate when the first mouse move occurs. This is
// used for the unbundled app activation hack, which needs to know
// if any mouse motions occurred prior to the app activating.
let delegate: id = msg_send![this, delegate];
assert_ne!(delegate, nil);
if !activation_hack::State::get_mouse_moved(&*delegate) {
activation_hack::State::set_mouse_moved(&*delegate, true);
let () = msg_send![
delegate,
performSelector: sel!(activationHackMouseMoved:)
withObject: nil
afterDelay: 0.0
];
}
}
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
let mut events = VecDeque::with_capacity(1);

View File

@@ -1,10 +1,10 @@
use super::{activation_hack, app_state::AppState};
use cocoa::base::id;
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel, BOOL, YES},
runtime::{Class, Object, Sel},
};
use crate::platform_impl::platform::app_state::AppState;
use std::os::raw::c_void;
pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
@@ -15,90 +15,67 @@ lazy_static! {
let superclass = class!(NSResponder);
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(&Object, Sel, id) -> BOOL,
did_finish_launching as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidBecomeActive:),
did_become_active as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillResignActive:),
will_resign_active as extern "C" fn(&Object, Sel, id),
sel!(applicationDidResignActive:),
did_resign_active as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>(activation_hack::State::name());
decl.add_method(
sel!(applicationWillEnterForeground:),
will_enter_foreground as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidEnterBackground:),
did_enter_background as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillTerminate:),
will_terminate as extern "C" fn(&Object, Sel, id),
sel!(activationHackMouseMoved:),
activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id),
);
AppDelegateClass(decl.register())
};
}
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL {
trace!("Triggered `didFinishLaunching`");
extern "C" fn new(class: &Class, _: Sel) -> id {
unsafe {
let this: id = msg_send![class, alloc];
let this: id = msg_send![this, init];
(*this).set_ivar(
activation_hack::State::name(),
activation_hack::State::new(),
);
this
}
}
extern "C" fn dealloc(this: &Object, _: Sel) {
unsafe {
activation_hack::State::free(activation_hack::State::get_ptr(this));
}
}
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidFinishLaunching`");
AppState::launched();
trace!("Completed `didFinishLaunching`");
YES
trace!("Completed `applicationDidFinishLaunching`");
}
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
trace!("Triggered `didBecomeActive`");
/*unsafe {
HANDLER.lock().unwrap().handle_nonuser_event(Event::Resumed)
}*/
trace!("Completed `didBecomeActive`");
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidBecomeActive`");
unsafe {
activation_hack::State::set_activated(this, true);
}
trace!("Completed `applicationDidBecomeActive`");
}
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
trace!("Triggered `willResignActive`");
/*unsafe {
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended)
}*/
trace!("Completed `willResignActive`");
}
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {
trace!("Triggered `willEnterForeground`");
trace!("Completed `willEnterForeground`");
}
extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {
trace!("Triggered `didEnterBackground`");
trace!("Completed `didEnterBackground`");
}
extern "C" fn will_terminate(_: &Object, _: Sel, _: id) {
trace!("Triggered `willTerminate`");
/*unsafe {
let app: id = msg_send![class!(UIApplication), sharedApplication];
let windows: id = msg_send![app, windows];
let windows_enum: id = msg_send![windows, objectEnumerator];
let mut events = Vec::new();
loop {
let window: id = msg_send![windows_enum, nextObject];
if window == nil {
break
}
let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)];
if is_winit_window == YES {
events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Destroyed,
});
}
}
HANDLER.lock().unwrap().handle_nonuser_events(events);
HANDLER.lock().unwrap().terminated();
}*/
trace!("Completed `willTerminate`");
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidResignActive`");
unsafe {
activation_hack::refocus(this);
}
trace!("Completed `applicationDidResignActive`");
}

View File

@@ -12,11 +12,13 @@ use std::{
};
use cocoa::{
appkit::{NSApp, NSWindow},
base::nil,
foundation::{NSAutoreleasePool, NSSize, NSString},
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize},
};
use objc::runtime::YES;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
@@ -29,7 +31,6 @@ use crate::{
},
window::WindowId,
};
use objc::runtime::Object;
lazy_static! {
static ref HANDLER: Handler = Default::default();
@@ -339,21 +340,23 @@ impl AppState {
let pool = NSAutoreleasePool::new(nil);
let windows: *const Object = msg_send![NSApp(), windows];
let window: *const Object = msg_send![windows, objectAtIndex:0];
let windows: id = msg_send![NSApp(), windows];
let window: id = msg_send![windows, objectAtIndex:0];
assert_ne!(window, nil);
let title: *const Object = msg_send![window, title];
assert_ne!(title, nil);
let postfix = NSString::alloc(nil).init_str("*");
let some_unique_title: *const Object =
msg_send![title, stringByAppendingString: postfix];
assert_ne!(some_unique_title, nil);
// To stop event loop immediately, we need to send some UI event here.
let _: () = msg_send![window, setTitle: some_unique_title];
// And restore it.
let _: () = msg_send![window, setTitle: title];
let dummy_event: id = msg_send![class!(NSEvent),
otherEventWithType: NSApplicationDefined
location: NSPoint::new(0.0, 0.0)
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0
];
// To stop event loop immediately, we need to post some event here.
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
pool.drain();
};

View File

@@ -287,6 +287,7 @@ pub unsafe fn modifier_event(
let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
#[allow(deprecated)]
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {

View File

@@ -1,5 +1,6 @@
#![cfg(target_os = "macos")]
mod activation_hack;
mod app;
mod app_delegate;
mod app_state;
@@ -24,6 +25,8 @@ use crate::{
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;

View File

@@ -1,15 +1,14 @@
use std::{collections::VecDeque, fmt};
use super::ffi;
use super::{ffi, util};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::util::IdRef,
};
use cocoa::{
appkit::NSScreen,
base::{id, nil},
foundation::{NSString, NSUInteger},
foundation::NSUInteger,
};
use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
@@ -303,7 +302,7 @@ impl MonitorHandle {
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count];
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let key = util::ns_string_id_ref("NSScreenNumber");
for i in 0..count {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
let device_description = NSScreen::deviceDescription(screen);

View File

@@ -122,7 +122,7 @@ extern "C" fn control_flow_begin_handler(
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopDestroyed would get sent after EventsCleared
// without that, LoopDestroyed would get sent after MainEventsCleared
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,

View File

@@ -1,20 +1,36 @@
use std::{
os::raw::c_void,
ops::Deref,
sync::{Mutex, Weak},
};
use cocoa::{
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize, NSString},
foundation::{NSPoint, NSSize, NSString},
};
use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f};
use dispatch::Queue;
use objc::rc::autoreleasepool;
use crate::{
dpi::LogicalSize,
platform_impl::platform::{ffi, util::IdRef, window::SharedState},
};
// Unsafe wrapper type that allows us to dispatch things that aren't Send.
// This should *only* be used to dispatch to the main queue.
// While it is indeed not guaranteed that these types can safely be sent to
// other threads, we know that they're safe to use on the main thread.
struct MainThreadSafe<T>(T);
unsafe impl<T> Send for MainThreadSafe<T> {}
impl<T> Deref for MainThreadSafe<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
ns_window.setStyleMask_(mask);
// If we don't do this, key handling will break
@@ -22,203 +38,55 @@ unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
ns_window.makeFirstResponder_(ns_view);
}
struct SetStyleMaskData {
ns_window: id,
ns_view: id,
mask: NSWindowStyleMask,
}
impl SetStyleMaskData {
fn new_ptr(ns_window: id, ns_view: id, mask: NSWindowStyleMask) -> *mut Self {
Box::into_raw(Box::new(SetStyleMaskData {
ns_window,
ns_view,
mask,
}))
}
}
extern "C" fn set_style_mask_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetStyleMaskData;
{
let context = &*context_ptr;
set_style_mask(context.ns_window, context.ns_view, context.mask);
}
Box::from_raw(context_ptr);
}
}
// Always use this function instead of trying to modify `styleMask` directly!
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
// Otherwise, this would vomit out errors about not being on the main thread
// and fail to do anything.
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
Queue::main().exec_async(move || {
set_style_mask(*ns_window, *ns_view, mask);
});
}
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
if msg_send![class!(NSThread), isMainThread] {
set_style_mask_callback(context as *mut _);
set_style_mask(ns_window, ns_view, mask);
} else {
dispatch_sync_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
Queue::main().exec_sync(move || {
set_style_mask(*ns_window, *ns_view, mask);
})
}
}
struct SetContentSizeData {
ns_window: id,
size: LogicalSize<f64>,
}
impl SetContentSizeData {
fn new_ptr(ns_window: id, size: LogicalSize<f64>) -> *mut Self {
Box::into_raw(Box::new(SetContentSizeData { ns_window, size }))
}
}
extern "C" fn set_content_size_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetContentSizeData;
{
let context = &*context_ptr;
NSWindow::setContentSize_(
context.ns_window,
NSSize::new(
context.size.width as CGFloat,
context.size.height as CGFloat,
),
);
}
Box::from_raw(context_ptr);
}
}
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
// and just fails silently. Anyway, GCD to the rescue!
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
let context = SetContentSizeData::new_ptr(ns_window, size);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_content_size_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat));
});
}
struct SetFrameTopLeftPointData {
ns_window: id,
point: NSPoint,
}
impl SetFrameTopLeftPointData {
fn new_ptr(ns_window: id, point: NSPoint) -> *mut Self {
Box::into_raw(Box::new(SetFrameTopLeftPointData { ns_window, point }))
}
}
extern "C" fn set_frame_top_left_point_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetFrameTopLeftPointData;
{
let context = &*context_ptr;
NSWindow::setFrameTopLeftPoint_(context.ns_window, context.point);
}
Box::from_raw(context_ptr);
}
}
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
// to log errors.
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) {
let context = SetFrameTopLeftPointData::new_ptr(ns_window, point);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_frame_top_left_point_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setFrameTopLeftPoint_(point);
});
}
struct SetLevelData {
ns_window: id,
level: ffi::NSWindowLevel,
}
impl SetLevelData {
fn new_ptr(ns_window: id, level: ffi::NSWindowLevel) -> *mut Self {
Box::into_raw(Box::new(SetLevelData { ns_window, level }))
}
}
extern "C" fn set_level_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetLevelData;
{
let context = &*context_ptr;
context.ns_window.setLevel_(context.level as _);
}
Box::from_raw(context_ptr);
}
}
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
let context = SetLevelData::new_ptr(ns_window, level);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_level_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setLevel_(level as _);
});
}
struct ToggleFullScreenData {
ns_window: id,
ns_view: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
}
impl ToggleFullScreenData {
fn new_ptr(
ns_window: id,
ns_view: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) -> *mut Self {
Box::into_raw(Box::new(ToggleFullScreenData {
ns_window,
ns_view,
not_fullscreen,
shared_state,
}))
}
}
extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut ToggleFullScreenData;
{
let context = &*context_ptr;
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
if context.not_fullscreen {
let curr_mask = context.ns_window.styleMask();
let required = NSWindowStyleMask::NSTitledWindowMask
| NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
set_style_mask(context.ns_window, context.ns_view, required);
if let Some(shared_state) = context.shared_state.upgrade() {
trace!("Locked shared state in `toggle_full_screen_callback`");
let mut shared_state_lock = shared_state.lock().unwrap();
(*shared_state_lock).saved_style = Some(curr_mask);
trace!("Unlocked shared state in `toggle_full_screen_callback`");
}
}
}
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
context.ns_window.setLevel_(0);
context.ns_window.toggleFullScreen_(nil);
}
Box::from_raw(context_ptr);
}
}
// `toggleFullScreen` is thread-safe, but our additional logic to account for
// window styles isn't.
pub unsafe fn toggle_full_screen_async(
@@ -227,91 +95,42 @@ pub unsafe fn toggle_full_screen_async(
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let context = ToggleFullScreenData::new_ptr(ns_window, ns_view, not_fullscreen, shared_state);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
toggle_full_screen_callback,
);
}
extern "C" fn restore_display_mode_callback(screen: *mut c_void) {
unsafe {
let screen = Box::from_raw(screen as *mut u32);
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess);
}
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
dispatch_async_f(
dispatch_get_main_queue(),
Box::into_raw(Box::new(ns_screen)) as *mut _,
restore_display_mode_callback,
);
}
struct SetMaximizedData {
ns_window: id,
is_zoomed: bool,
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
}
impl SetMaximizedData {
fn new_ptr(
ns_window: id,
is_zoomed: bool,
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
) -> *mut Self {
Box::into_raw(Box::new(SetMaximizedData {
ns_window,
is_zoomed,
maximized,
shared_state,
}))
}
}
extern "C" fn set_maximized_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetMaximizedData;
{
let context = &*context_ptr;
if let Some(shared_state) = context.shared_state.upgrade() {
trace!("Locked shared state in `set_maximized`");
let mut shared_state_lock = shared_state.lock().unwrap();
// Save the standard frame sized if it is not zoomed
if !context.is_zoomed {
shared_state_lock.standard_frame = Some(NSWindow::frame(context.ns_window));
let ns_window = MainThreadSafe(ns_window);
let ns_view = MainThreadSafe(ns_view);
let shared_state = MainThreadSafe(shared_state);
Queue::main().exec_async(move || {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
if not_fullscreen {
let curr_mask = ns_window.styleMask();
let required =
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
set_style_mask(*ns_window, *ns_view, required);
if let Some(shared_state) = shared_state.upgrade() {
trace!("Locked shared state in `toggle_full_screen_callback`");
let mut shared_state_lock = shared_state.lock().unwrap();
(*shared_state_lock).saved_style = Some(curr_mask);
trace!("Unlocked shared state in `toggle_full_screen_callback`");
}
shared_state_lock.maximized = context.maximized;
let curr_mask = context.ns_window.styleMask();
if shared_state_lock.fullscreen.is_some() {
// Handle it in window_did_exit_fullscreen
return;
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
// Just use the native zoom if resizable
context.ns_window.zoom_(nil);
} else {
// if it's not resizable, we set the frame directly
let new_rect = if context.maximized {
let screen = NSScreen::mainScreen(nil);
NSScreen::visibleFrame(screen)
} else {
shared_state_lock.saved_standard_frame()
};
context.ns_window.setFrame_display_(new_rect, 0);
}
trace!("Unlocked shared state in `set_maximized`");
}
}
Box::from_raw(context_ptr);
}
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
ns_window.setLevel_(0);
ns_window.toggleFullScreen_(nil);
});
}
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
Queue::main().exec_async(move || {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
});
}
// `setMaximized` is not thread-safe
pub unsafe fn set_maximized_async(
ns_window: id,
@@ -319,127 +138,79 @@ pub unsafe fn set_maximized_async(
maximized: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let context = SetMaximizedData::new_ptr(ns_window, is_zoomed, maximized, shared_state);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_maximized_callback,
);
let ns_window = MainThreadSafe(ns_window);
let shared_state = MainThreadSafe(shared_state);
Queue::main().exec_async(move || {
if let Some(shared_state) = shared_state.upgrade() {
trace!("Locked shared state in `set_maximized`");
let mut shared_state_lock = shared_state.lock().unwrap();
// Save the standard frame sized if it is not zoomed
if !is_zoomed {
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window));
}
shared_state_lock.maximized = maximized;
let curr_mask = ns_window.styleMask();
if shared_state_lock.fullscreen.is_some() {
// Handle it in window_did_exit_fullscreen
return;
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
// Just use the native zoom if resizable
ns_window.zoom_(nil);
} else {
// if it's not resizable, we set the frame directly
let new_rect = if maximized {
let screen = NSScreen::mainScreen(nil);
NSScreen::visibleFrame(screen)
} else {
shared_state_lock.saved_standard_frame()
};
ns_window.setFrame_display_(new_rect, 0);
}
trace!("Unlocked shared state in `set_maximized`");
}
});
}
struct OrderOutData {
ns_window: id,
}
impl OrderOutData {
fn new_ptr(ns_window: id) -> *mut Self {
Box::into_raw(Box::new(OrderOutData { ns_window }))
}
}
extern "C" fn order_out_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut OrderOutData;
{
let context = &*context_ptr;
context.ns_window.orderOut_(nil);
}
Box::from_raw(context_ptr);
}
}
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
// but with an odd delay.
pub unsafe fn order_out_async(ns_window: id) {
let context = OrderOutData::new_ptr(ns_window);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
order_out_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.orderOut_(nil);
});
}
struct MakeKeyAndOrderFrontData {
ns_window: id,
}
impl MakeKeyAndOrderFrontData {
fn new_ptr(ns_window: id) -> *mut Self {
Box::into_raw(Box::new(MakeKeyAndOrderFrontData { ns_window }))
}
}
extern "C" fn make_key_and_order_front_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut MakeKeyAndOrderFrontData;
{
let context = &*context_ptr;
context.ns_window.makeKeyAndOrderFront_(nil);
}
Box::from_raw(context_ptr);
}
}
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
// actually works, but with an odd delay.
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
let context = MakeKeyAndOrderFrontData::new_ptr(ns_window);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
make_key_and_order_front_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.makeKeyAndOrderFront_(nil);
});
}
struct SetTitleData {
ns_window: id,
title: String,
}
impl SetTitleData {
fn new_ptr(ns_window: id, title: String) -> *mut Self {
Box::into_raw(Box::new(SetTitleData { ns_window, title }))
}
}
extern "C" fn set_title_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetTitleData;
{
let context = &*context_ptr;
let title = IdRef::new(NSString::alloc(nil).init_str(&context.title));
context.ns_window.setTitle_(*title);
}
Box::from_raw(context_ptr);
}
}
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
// window drag regions, which throws an exception when not done in the main
// thread
pub unsafe fn set_title_async(ns_window: id, title: String) {
let context = SetTitleData::new_ptr(ns_window, title);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_title_callback,
);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
ns_window.setTitle_(*title);
});
}
struct CloseData {
ns_window: id,
}
impl CloseData {
fn new_ptr(ns_window: id) -> *mut Self {
Box::into_raw(Box::new(CloseData { ns_window }))
}
}
extern "C" fn close_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut CloseData;
{
let context = &*context_ptr;
let pool = NSAutoreleasePool::new(nil);
context.ns_window.close();
pool.drain();
}
Box::from_raw(context_ptr);
}
}
// `close:` is thread-safe, but we want the event to be triggered from the main
// thread. Though, it's a good idea to look into that more...
pub unsafe fn close_async(ns_window: id) {
let context = CloseData::new_ptr(ns_window);
dispatch_async_f(dispatch_get_main_queue(), context as *mut _, close_callback);
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
autoreleasepool(move || {
ns_window.close();
});
});
}

View File

@@ -8,7 +8,7 @@ use std::ops::{BitAnd, Deref};
use cocoa::{
appkit::{NSApp, NSWindowStyleMask},
base::{id, nil},
foundation::{NSAutoreleasePool, NSRect, NSUInteger},
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
};
use core_graphics::display::CGDisplay;
use objc::runtime::{Class, Object, Sel, BOOL, YES};
@@ -91,6 +91,22 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
IdRef::new(NSString::alloc(nil).init_str(s))
}
pub unsafe fn app_name() -> Option<id> {
let bundle: id = msg_send![class!(NSBundle), mainBundle];
let dict: id = msg_send![bundle, infoDictionary];
let key = ns_string_id_ref("CFBundleName");
let app_name: id = msg_send![dict, objectForKey:*key];
if app_name != nil {
Some(app_name)
} else {
None
}
}
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
let superclass: id = msg_send![this, superclass];
&*(superclass as *const _)

View File

@@ -17,6 +17,7 @@ use objc::{
};
use crate::{
dpi::LogicalPosition,
event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton,
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
@@ -49,16 +50,22 @@ impl Default for CursorState {
}
}
struct ViewState {
pub(super) struct ViewState {
ns_window: id,
pub cursor_state: Arc<Mutex<CursorState>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
modifiers: ModifiersState,
pub(super) modifiers: ModifiersState,
tracking_rect: Option<NSInteger>,
}
impl ViewState {
fn get_scale_factor(&self) -> f64 {
(unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64
}
}
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
let cursor_state = Default::default();
let cursor_access = Arc::downgrade(&cursor_state);
@@ -616,6 +623,19 @@ fn retrieve_keycode(event: id) -> Option<VirtualKeyCode> {
})
}
// Update `state.modifiers` if `event` has something different
fn update_potentially_stale_modifiers(state: &mut ViewState, event: id) {
let event_modifiers = event_mods(event);
if state.modifiers != event_modifiers {
state.modifiers = event_modifiers;
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::ModifiersChanged(state.modifiers),
}));
}
}
extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `keyDown`");
unsafe {
@@ -631,6 +651,9 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
let is_repeat = msg_send![event, isARepeat];
update_potentially_stale_modifiers(state, event);
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
@@ -683,6 +706,9 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
update_potentially_stale_modifiers(state, event);
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::KeyboardInput {
@@ -746,16 +772,18 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
events.push_back(window_event);
}
let window_id = WindowId(get_window_id(state.ns_window));
for event in events {
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
window_id,
event,
}));
}
AppState::queue_event(EventWrapper::StaticEvent(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::ModifiersChanged(state.modifiers),
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(state.modifiers),
}));
}
trace!("Completed `flagsChanged`");
@@ -797,6 +825,9 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
let event: id = msg_send![NSApp(), currentEvent];
update_potentially_stale_modifiers(state, event);
#[allow(deprecated)]
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::KeyboardInput {
@@ -821,6 +852,8 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
update_potentially_stale_modifiers(state, event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::MouseInput {
@@ -882,12 +915,15 @@ fn mouse_motion(this: &Object, event: id) {
let x = view_point.x as f64;
let y = view_rect.size.height as f64 - view_point.y as f64;
let logical_position = LogicalPosition::new(x, y);
update_potentially_stale_modifiers(state, event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
position: logical_position.to_physical(state.get_scale_factor()),
modifiers: event_mods(event),
},
};
@@ -912,7 +948,7 @@ extern "C" fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
extern "C" fn mouse_entered(this: &Object, _sel: Sel, _event: id) {
trace!("Triggered `mouseEntered`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
@@ -925,27 +961,7 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
},
};
let move_event = {
let window_point = event.locationInWindow();
let view_point: NSPoint = msg_send![this,
convertPoint:window_point
fromView:nil // convert from window coordinates
];
let view_rect: NSRect = msg_send![this, frame];
let x = view_point.x as f64;
let y = (view_rect.size.height - view_point.y) as f64;
Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(event),
},
}
};
AppState::queue_event(EventWrapper::StaticEvent(enter_event));
AppState::queue_event(EventWrapper::StaticEvent(move_event));
}
trace!("Completed `mouseEntered`");
}
@@ -995,6 +1011,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
update_potentially_stale_modifiers(state, event);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.ns_window)),
event: WindowEvent::MouseWheel {

View File

@@ -36,7 +36,7 @@ use cocoa::{
NSWindow, NSWindowButton, NSWindowStyleMask,
},
base::{id, nil},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize},
};
use core_graphics::display::{CGDisplay, CGDisplayMode};
use objc::{
@@ -150,10 +150,14 @@ fn create_window(
let mut masks = if !attrs.decorations && !screen.is_some() {
// Resizable UnownedWindow without a titlebar or borders
// if decorations is set to false, ignore pl_attrs
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
} else if pl_attrs.titlebar_hidden {
// if the titlebar is hidden, ignore other pl_attrs
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
} else {
// default case, resizable window with titlebar and titlebar buttons
NSWindowStyleMask::NSClosableWindowMask
@@ -178,7 +182,7 @@ fn create_window(
NO,
));
let res = ns_window.non_nil().map(|ns_window| {
let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title));
let title = util::ns_string_id_ref(&attrs.title);
ns_window.setReleasedWhenClosed_(NO);
ns_window.setTitle_(*title);
ns_window.setAcceptsMouseMovedEvents_(YES);
@@ -336,7 +340,7 @@ impl UnownedWindow {
let input_context = unsafe { util::create_input_context(*ns_view) };
let dpi_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
unsafe {
if win_attribs.transparent {
@@ -346,11 +350,11 @@ impl UnownedWindow {
ns_app.activateIgnoringOtherApps_(YES);
win_attribs.min_inner_size.map(|dim| {
let logical_dim = dim.to_logical(dpi_factor);
let logical_dim = dim.to_logical(scale_factor);
set_min_inner_size(*ns_window, logical_dim)
});
win_attribs.max_inner_size.map(|dim| {
let logical_dim = dim.to_logical(dpi_factor);
let logical_dim = dim.to_logical(scale_factor);
set_max_inner_size(*ns_window, logical_dim)
});
@@ -374,7 +378,7 @@ impl UnownedWindow {
let decorations = win_attribs.decorations;
let inner_rect = win_attribs
.inner_size
.map(|size| size.to_physical(dpi_factor));
.map(|size| size.to_physical(scale_factor));
let window = Arc::new(UnownedWindow {
ns_view,
@@ -446,8 +450,8 @@ impl UnownedWindow {
frame_rect.origin.x as f64,
util::bottom_left_to_top_left(frame_rect),
);
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
@@ -458,13 +462,13 @@ impl UnownedWindow {
content_rect.origin.x as f64,
util::bottom_left_to_top_left(content_rect),
);
let dpi_factor = self.scale_factor();
Ok(position.to_physical(dpi_factor))
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn set_outer_position(&self, position: Position) {
let dpi_factor = self.scale_factor();
let position = position.to_logical(dpi_factor);
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
let dummy = NSRect::new(
NSPoint::new(
position.x,
@@ -484,8 +488,8 @@ impl UnownedWindow {
let view_frame = unsafe { NSView::frame(*self.ns_view) };
let logical: LogicalSize<f64> =
(view_frame.size.width as f64, view_frame.size.height as f64).into();
let dpi_factor = self.scale_factor();
logical.to_physical(dpi_factor)
let scale_factor = self.scale_factor();
logical.to_physical(scale_factor)
}
#[inline]
@@ -493,15 +497,15 @@ impl UnownedWindow {
let view_frame = unsafe { NSWindow::frame(*self.ns_window) };
let logical: LogicalSize<f64> =
(view_frame.size.width as f64, view_frame.size.height as f64).into();
let dpi_factor = self.scale_factor();
logical.to_physical(dpi_factor)
let scale_factor = self.scale_factor();
logical.to_physical(scale_factor)
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
unsafe {
let dpi_factor = self.scale_factor();
util::set_content_size_async(*self.ns_window, size.to_logical(dpi_factor));
let scale_factor = self.scale_factor();
util::set_content_size_async(*self.ns_window, size.to_logical(scale_factor));
}
}
@@ -511,8 +515,8 @@ impl UnownedWindow {
width: 0.0,
height: 0.0,
}));
let dpi_factor = self.scale_factor();
set_min_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
let scale_factor = self.scale_factor();
set_min_inner_size(*self.ns_window, dimensions.to_logical(scale_factor));
}
}
@@ -522,8 +526,8 @@ impl UnownedWindow {
width: std::f32::MAX as f64,
height: std::f32::MAX as f64,
}));
let dpi_factor = self.scale_factor();
set_max_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
let scale_factor = self.scale_factor();
set_max_inner_size(*self.ns_window, dimensions.to_logical(scale_factor));
}
}
@@ -590,9 +594,9 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> {
let physical_window_position = self.inner_position().unwrap();
let dpi_factor = self.scale_factor();
let window_position = physical_window_position.to_logical::<CGFloat>(dpi_factor);
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(dpi_factor);
let scale_factor = self.scale_factor();
let window_position = physical_window_position.to_logical::<CGFloat>(scale_factor);
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(scale_factor);
let point = appkit::CGPoint {
x: logical_cursor_position.x + window_position.x,
y: logical_cursor_position.y + window_position.y,
@@ -929,8 +933,8 @@ impl UnownedWindow {
#[inline]
pub fn set_ime_position(&self, spot: Position) {
let dpi_factor = self.scale_factor();
let logical_spot = spot.to_logical(dpi_factor);
let scale_factor = self.scale_factor();
let logical_spot = spot.to_logical(scale_factor);
unsafe {
view::set_ime_position(
*self.ns_view,
@@ -946,7 +950,7 @@ impl UnownedWindow {
unsafe {
let screen: id = msg_send![*self.ns_window, screen];
let desc = NSScreen::deviceDescription(screen);
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let key = util::ns_string_id_ref("NSScreenNumber");
let value = NSDictionary::valueForKey_(desc, *key);
let display_id = msg_send![value, unsignedIntegerValue];
RootMonitorHandle {
@@ -1024,7 +1028,11 @@ impl WindowExtMacOS for UnownedWindow {
if fullscreen {
// Remember the original window's settings
shared_state_lock.standard_frame = Some(NSWindow::frame(*self.ns_window));
// Exclude title bar
shared_state_lock.standard_frame = Some(NSWindow::contentRectForFrameRect_(
*self.ns_window,
NSWindow::frame(*self.ns_window),
));
shared_state_lock.saved_style = Some(self.ns_window.styleMask());
shared_state_lock.save_presentation_opts = Some(app.presentationOptions_());

View File

@@ -16,11 +16,12 @@ use objc::{
use crate::{
dpi::LogicalSize,
event::{Event, WindowEvent},
event::{Event, ModifiersState, WindowEvent},
platform_impl::platform::{
app_state::AppState,
event::{EventProxy, EventWrapper},
util::{self, IdRef},
view::ViewState,
window::{get_window_id, UnownedWindow},
},
window::{Fullscreen, WindowId},
@@ -43,7 +44,7 @@ pub struct WindowDelegateState {
previous_position: Option<(f64, f64)>,
// Used to prevent redundant events.
previous_dpi_factor: f64,
previous_scale_factor: f64,
}
impl WindowDelegateState {
@@ -55,7 +56,7 @@ impl WindowDelegateState {
window: Arc::downgrade(&window),
initial_fullscreen,
previous_position: None,
previous_dpi_factor: scale_factor,
previous_scale_factor: scale_factor,
};
if scale_factor != 1.0 {
@@ -82,11 +83,11 @@ impl WindowDelegateState {
pub fn emit_static_scale_factor_changed_event(&mut self) {
let scale_factor = self.get_scale_factor();
if scale_factor == self.previous_dpi_factor {
if scale_factor == self.previous_scale_factor {
return ();
};
self.previous_dpi_factor = scale_factor;
self.previous_scale_factor = scale_factor;
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
ns_window: IdRef::retain(*self.ns_window),
suggested_size: self.view_size(),
@@ -319,6 +320,29 @@ extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) {
extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidResignKey:`");
with_state(this, |state| {
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
// NSWindowDelegate will receive a didResignKey event despite no event
// being received when the modifiers are released. This is because
// flagsChanged events are received by the NSView instead of the
// NSWindowDelegate, and as a result a tracked modifiers state can quite
// easily fall out of synchrony with reality. This requires us to emit
// a synthetic ModifiersChanged event when we lose focus.
//
// Here we (very unsafely) acquire the winitState (a ViewState) from the
// Object referenced by state.ns_view (an IdRef, which is dereferenced
// to an id)
let view_state: &mut ViewState = unsafe {
let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref");
let state_ptr: *mut c_void = *ns_view.get_ivar("winitState");
&mut *(state_ptr as *mut ViewState)
};
// Both update the state and emit a ModifiersChanged event.
if !view_state.modifiers.is_empty() {
view_state.modifiers = ModifiersState::empty();
state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers));
}
state.emit_event(WindowEvent::Focused(false));
});
trace!("Completed `windowDidResignKey:`");

View File

@@ -8,6 +8,7 @@ use std::{
cell::RefCell,
clone::Clone,
collections::{HashSet, VecDeque},
iter,
rc::Rc,
};
@@ -60,7 +61,7 @@ impl<T: 'static> Shared<T> {
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
) {
self.0.runner.replace(Some(Runner::new(event_handler)));
self.send_event(Event::NewEvents(StartCause::Init));
self.init();
let close_instance = self.clone();
backend::on_unload(move || close_instance.handle_unload());
@@ -79,55 +80,98 @@ impl<T: 'static> Shared<T> {
self.0.redraw_pending.borrow_mut().insert(id);
}
// Add an event to the event loop runner
pub fn init(&self) {
let start_cause = Event::NewEvents(StartCause::Init);
self.run_until_cleared(iter::once(start_cause));
}
// Run the polling logic for the Poll ControlFlow, which involves clearing the queue
pub fn poll(&self) {
let start_cause = Event::NewEvents(StartCause::Poll);
self.run_until_cleared(iter::once(start_cause));
}
// Run the logic for waking from a WaitUntil, which involves clearing the queue
// Generally there shouldn't be events built up when this is called
pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) {
let start_cause = Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
});
self.run_until_cleared(iter::once(start_cause));
}
// Add an event to the event loop runner, from the user or an event handler
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub fn send_event(&self, event: Event<'static, T>) {
self.send_events(iter::once(event));
}
// Add a series of events to the event loop runner
//
// It will determine if the event should be immediately sent to the user or buffered for later
pub fn send_events(&self, events: impl Iterator<Item = Event<'static, T>>) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
}
// Determine if event handling is in process, and then release the borrow on the runner
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
Some(ref runner) if !runner.is_busy => {
if let Event::NewEvents(cause) = event {
(cause, true)
} else {
(
match runner.state {
State::Init => StartCause::Init,
State::Poll { .. } => StartCause::Poll,
State::Wait { start } => StartCause::WaitCancelled {
start,
requested_resume: None,
},
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
start,
requested_resume: Some(end),
},
State::Exit => {
return;
}
},
false,
)
// If we can run the event processing right now, or need to queue this and wait for later
let mut process_immediately = true;
if let Some(ref runner) = &*self.0.runner.borrow() {
// If we're currently polling, queue this and wait for the poll() method to be called
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
// If the runner is busy, queue this and wait for it to process it later
if runner.is_busy {
process_immediately = false;
}
} else {
// The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false;
}
if !process_immediately {
// Queue these events to look at later
self.0.events.borrow_mut().extend(events);
return;
}
// At this point, we know this is a fresh set of events
// Now we determine why new events are incoming, and handle the events
let start_cause = if let Some(runner) = &*self.0.runner.borrow() {
match runner.state {
State::Init => StartCause::Init,
State::Poll { .. } => StartCause::Poll,
State::Wait { start } => StartCause::WaitCancelled {
start,
requested_resume: None,
},
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
start,
requested_resume: Some(end),
},
State::Exit => {
// If we're in the exit state, don't do event processing
return;
}
}
_ => {
// Events are currently being handled, so queue this one and don't try to
// double-process the event queue
self.0.events.borrow_mut().push_back(event);
return;
}
} else {
unreachable!("The runner cannot process events when it is not attached");
};
// Take the start event, then the events provided to this function, and run an iteration of
// the event loop
let start_event = Event::NewEvents(start_cause);
let events = iter::once(start_event).chain(events);
self.run_until_cleared(events);
}
// Given the set of new events, run the event loop until the main events and redraw events are
// cleared
//
// This will also process any events that have been queued or that are queued during processing
fn run_until_cleared(&self, events: impl Iterator<Item = Event<'static, T>>) {
let mut control = self.current_control_flow();
// Handle starting a new batch of events
//
// The user is informed via Event::NewEvents that there is a batch of events to process
// However, there is only one of these per batch of events
self.handle_event(Event::NewEvents(start_cause), &mut control);
if !event_is_start {
for event in events {
self.handle_event(event, &mut control);
}
self.handle_event(Event::MainEventsCleared, &mut control);
@@ -196,10 +240,7 @@ impl<T: 'static> Shared<T> {
root::ControlFlow::Poll => {
let cloned = self.clone();
State::Poll {
timeout: backend::Timeout::new(
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
Duration::from_millis(0),
),
timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)),
}
}
root::ControlFlow::Wait => State::Wait {
@@ -220,7 +261,7 @@ impl<T: 'static> Shared<T> {
start,
end,
timeout: backend::Timeout::new(
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
move || cloned.resume_time_reached(start, end),
delay,
),
}

View File

@@ -2,7 +2,7 @@ use super::{backend, device, proxy::Proxy, runner, window};
use crate::dpi::{PhysicalSize, Size};
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
use crate::window::{Theme, WindowId};
use std::clone::Clone;
pub struct WindowTarget<T: 'static> {
@@ -57,6 +57,7 @@ impl<T> WindowTarget<T> {
let runner = self.runner.clone();
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
#[allow(deprecated)]
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
@@ -74,6 +75,7 @@ impl<T> WindowTarget<T> {
let runner = self.runner.clone();
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
#[allow(deprecated)]
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::KeyboardInput {
@@ -197,5 +199,18 @@ impl<T> WindowTarget<T> {
});
runner.request_redraw(WindowId(id));
});
let runner = self.runner.clone();
canvas.on_dark_mode(move |is_dark_mode| {
let theme = if is_dark_mode {
Theme::Dark
} else {
Theme::Light
};
runner.send_event(Event::WindowEvent {
window_id: WindowId(id),
event: WindowEvent::ThemeChanged(theme),
});
});
}
}

View File

@@ -1,6 +1,21 @@
// TODO: close events (port from old stdweb branch)
// TODO: pointer locking (stdweb PR required)
// TODO: fullscreen API (stdweb PR required)
// Brief introduction to the internals of the web backend:
// Currently, the web backend supports both wasm-bindgen and stdweb as methods of binding to the
// environment. Because they are both supporting the same underlying APIs, the actual web bindings
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
//
// When adding support for new events or interactions with the browser, first consult trusted
// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers.
// Once you have decided on the relevant web APIs, add support to both backends.
//
// The backend is used by the rest of the module to implement Winit's business logic, which forms
// the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures
// for winit's cross-platform structures. They are all relatively simple translations.
//
// The event_loop module handles listening for and processing events. 'Proxy' implements
// EventLoopProxy and 'WindowTarget' implements EventLoopWindowTarget. WindowTarget also handles
// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
// compliant way.
mod device;
mod error;
@@ -29,3 +44,5 @@ pub use self::window::{
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
Window,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;

View File

@@ -2,10 +2,11 @@ use super::event;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
use std::cell::RefCell;
use std::rc::Rc;
use stdweb::js;
use stdweb::traits::IPointerEvent;
use stdweb::unstable::TryInto;
use stdweb::web::event::{
@@ -43,12 +44,15 @@ impl Drop for Canvas {
}
impl Canvas {
pub fn create() -> Result<Self, RootOE> {
let canvas: CanvasElement = document()
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.try_into()
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
let canvas = match attr.canvas {
Some(canvas) => canvas,
None => document()
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.try_into()
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?,
};
// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
@@ -207,7 +211,7 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
{
// todo
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
@@ -237,6 +241,22 @@ impl Canvas {
self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler()));
}
pub fn on_dark_mode<F>(&mut self, handler: F)
where
F: 'static + FnMut(bool),
{
// TODO: upstream to stdweb
js! {
var handler = @{handler};
if (window.matchMedia) {
window.matchMedia("(prefers-color-scheme: dark)").addListener(function(e) {
handler(event.matches)
});
}
}
}
fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
where
E: ConcreteEvent,

View File

@@ -10,6 +10,7 @@ use crate::platform::web::WindowExtStdweb;
use crate::window::Window;
use stdweb::js;
use stdweb::unstable::TryInto;
use stdweb::web::event::BeforeUnloadEvent;
use stdweb::web::window;
use stdweb::web::IEventTarget;
@@ -31,6 +32,15 @@ impl WindowExtStdweb for Window {
fn canvas(&self) -> CanvasElement {
self.window.canvas().raw().clone()
}
fn is_dark_mode(&self) -> bool {
// TODO: upstream to stdweb
let is_dark_mode = js! {
return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)
};
is_dark_mode.try_into().expect("should return a bool")
}
}
pub fn window_size() -> LogicalSize<f64> {
@@ -72,3 +82,5 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
None => false,
}
}
pub type RawCanvasType = CanvasElement;

View File

@@ -2,13 +2,16 @@ use super::event;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
use crate::platform_impl::OsError;
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
use web_sys::{
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, PointerEvent,
WheelEvent,
};
pub struct Canvas {
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
@@ -26,6 +29,7 @@ pub struct Canvas {
on_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
wants_fullscreen: Rc<RefCell<bool>>,
on_dark_mode: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
}
impl Drop for Canvas {
@@ -35,18 +39,23 @@ impl Drop for Canvas {
}
impl Canvas {
pub fn create() -> Result<Self, RootOE> {
let window =
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
let canvas = match attr.canvas {
Some(canvas) => canvas,
None => {
let window = web_sys::window()
.ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
let document = window
.document()
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
let document = window
.document()
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.unchecked_into();
document
.create_element("canvas")
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
.unchecked_into()
}
};
// A tabindex is needed in order to capture local keyboard events.
// A "0" value means that the element should be focusable in
@@ -72,6 +81,7 @@ impl Canvas {
on_mouse_wheel: None,
on_fullscreen_change: None,
wants_fullscreen: Rc::new(RefCell::new(false)),
on_dark_mode: None,
})
}
@@ -216,7 +226,7 @@ impl Canvas {
pub fn on_cursor_move<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
{
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
handler(
@@ -246,6 +256,28 @@ impl Canvas {
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
}
pub fn on_dark_mode<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(bool),
{
let window = web_sys::window().expect("Failed to obtain window");
self.on_dark_mode = window
.match_media("(prefers-color-scheme: dark)")
.ok()
.flatten()
.and_then(|media| {
let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| {
handler(event.matches())
}) as Box<dyn FnMut(_)>);
media
.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref()))
.map(|_| closure)
.ok()
});
}
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,

View File

@@ -38,6 +38,17 @@ impl WindowExtWebSys for Window {
fn canvas(&self) -> HtmlCanvasElement {
self.window.canvas().raw().clone()
}
fn is_dark_mode(&self) -> bool {
let window = web_sys::window().expect("Failed to obtain window");
window
.match_media("(prefers-color-scheme: dark)")
.ok()
.flatten()
.map(|media| media.matches())
.unwrap_or(false)
}
}
pub fn window_size() -> LogicalSize<f64> {
@@ -91,3 +102,5 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
None => false,
}
}
pub type RawCanvasType = HtmlCanvasElement;

View File

@@ -23,13 +23,13 @@ impl Window {
pub fn new<T>(
target: &EventLoopWindowTarget<T>,
attr: WindowAttributes,
_: PlatformSpecificBuilderAttributes,
platform_attr: PlatformSpecificBuilderAttributes,
) -> Result<Self, RootOE> {
let runner = target.runner.clone();
let id = target.generate_id();
let mut canvas = backend::Canvas::create()?;
let mut canvas = backend::Canvas::create(platform_attr)?;
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
@@ -281,5 +281,7 @@ impl Id {
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PlatformSpecificBuilderAttributes;
#[derive(Default, Clone)]
pub struct PlatformSpecificBuilderAttributes {
pub(crate) canvas: Option<backend::RawCanvasType>,
}

View File

@@ -6,9 +6,10 @@ use std::os::windows::ffi::OsStrExt;
use winapi::{
shared::{
basetsd::SIZE_T,
minwindef::{BOOL, DWORD, UINT, ULONG, WORD},
minwindef::{BOOL, DWORD, FALSE, UINT, ULONG, WORD},
ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR},
windef::HWND,
winerror::S_OK,
},
um::{libloaderapi, uxtheme, winuser},
};
@@ -44,9 +45,8 @@ lazy_static! {
};
let status = (rtl_get_version)(&mut vi as _);
assert!(NT_SUCCESS(status));
if vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
if NT_SUCCESS(status) && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
Some(vi.dwBuildNumber)
} else {
None
@@ -82,22 +82,15 @@ pub fn try_dark_mode(hwnd: HWND) -> bool {
LIGHT_THEME_NAME.as_ptr()
};
unsafe {
assert_eq!(
0,
uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null())
);
let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) };
set_dark_mode_for_window(hwnd, is_dark_mode)
}
is_dark_mode
status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode)
} else {
false
}
}
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
// Uses Windows undocumented API SetWindowCompositionAttribute,
// as seen in win32-darkmode example linked at top of file.
@@ -132,11 +125,12 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
};
assert_eq!(
1,
set_window_composition_attribute(hwnd, &mut data as *mut _)
);
let status = set_window_composition_attribute(hwnd, &mut data as *mut _);
status != FALSE
}
} else {
false
}
}
@@ -205,7 +199,7 @@ fn is_high_contrast() -> bool {
)
};
(ok > 0) && ((HCF_HIGHCONTRASTON & hc.dwFlags) == 1)
ok != FALSE && (HCF_HIGHCONTRASTON & hc.dwFlags) == 1
}
fn widestring(src: &'static str) -> Vec<u16> {

View File

@@ -50,9 +50,7 @@ use crate::{
dark_mode::try_dark_mode,
dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling},
drop_handler::FileDropHandler,
event::{
self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide,
},
event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey},
monitor, raw_input, util,
window_state::{CursorFlags, WindowFlags, WindowState},
wrap_device_id, WindowId, DEVICE_ID,
@@ -100,23 +98,18 @@ pub(crate) struct SubclassInput<T: 'static> {
}
impl<T> SubclassInput<T> {
unsafe fn send_event(&self, event: Event<'static, T>) {
unsafe fn send_event(&self, event: Event<'_, T>) {
self.event_loop_runner.send_event(event);
}
unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> {
self.event_loop_runner.send_event_unbuffered(event)
}
}
struct ThreadMsgTargetSubclassInput<T: 'static> {
event_loop_runner: EventLoopRunnerShared<T>,
user_event_receiver: Receiver<T>,
modifiers_state: ModifiersStateSide,
}
impl<T> ThreadMsgTargetSubclassInput<T> {
unsafe fn send_event(&self, event: Event<'static, T>) {
unsafe fn send_event(&self, event: Event<'_, T>) {
self.event_loop_runner.send_event(event);
}
}
@@ -216,49 +209,77 @@ impl<T: 'static> EventLoop<T> {
unsafe {
let mut msg = mem::zeroed();
let mut msg_unprocessed = false;
let mut unread_message_exists = false;
'main: loop {
if let Err(payload) = runner.take_panic_error() {
runner.destroy_runner();
panic::resume_unwind(payload);
}
runner.new_events();
loop {
if !msg_unprocessed {
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) {
if !unread_message_exists {
if 0 == winuser::PeekMessageW(
&mut msg,
ptr::null_mut(),
0,
0,
winuser::PM_REMOVE,
) {
break;
}
}
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
msg_unprocessed = false;
}
runner.events_cleared();
if let Err(payload) = runner.take_panic_error() {
runner.destroy_runner();
panic::resume_unwind(payload);
}
unread_message_exists = false;
if !msg_unprocessed {
let control_flow = runner.control_flow();
match control_flow {
ControlFlow::Exit => break 'main,
ControlFlow::Wait => {
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
break 'main;
}
msg_unprocessed = true;
}
ControlFlow::WaitUntil(resume_time) => {
wait_until_time_or_msg(resume_time);
}
ControlFlow::Poll => (),
if msg.message == winuser::WM_PAINT {
// An "external" redraw was requested.
// Note that the WM_PAINT has been dispatched and
// has caused the event loop to emit the MainEventsCleared event.
// See EventLoopRunner::process_event().
// The call to main_events_cleared() below will do nothing.
break;
}
}
// Make sure we emit the MainEventsCleared event if no WM_PAINT message was received.
runner.main_events_cleared();
// Drain eventual WM_PAINT messages sent if user called request_redraw()
// during handling of MainEventsCleared.
loop {
if 0 == winuser::PeekMessageW(
&mut msg,
ptr::null_mut(),
winuser::WM_PAINT,
winuser::WM_PAINT,
winuser::PM_QS_PAINT | winuser::PM_REMOVE,
) {
break;
}
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
}
runner.redraw_events_cleared();
match runner.control_flow() {
ControlFlow::Exit => break 'main,
ControlFlow::Wait => {
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
break 'main;
}
unread_message_exists = true;
}
ControlFlow::WaitUntil(resume_time) => {
wait_until_time_or_msg(resume_time);
}
ControlFlow::Poll => (),
}
}
}
unsafe {
runner.call_event_handler(Event::LoopDestroyed);
}
runner.destroy_loop();
runner.destroy_runner();
}
@@ -295,11 +316,9 @@ fn main_thread_id() -> DWORD {
unsafe { MAIN_THREAD_ID }
}
// Returns true if the wait time was reached, and false if a message must be processed.
unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool {
let mut msg = mem::zeroed();
unsafe fn wait_until_time_or_msg(wait_until: Instant) {
let now = Instant::now();
if now <= wait_until {
if now < wait_until {
// MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond
// from the requested time and spinlock for the remainder to compensate for that.
let resume_reason = winuser::MsgWaitForMultipleObjectsEx(
@@ -311,16 +330,16 @@ unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool {
);
if resume_reason == winerror::WAIT_TIMEOUT {
let mut msg = mem::zeroed();
while Instant::now() < wait_until {
if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
return false;
break;
}
}
}
}
return true;
}
// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs
fn dur2timeout(dur: Duration) -> DWORD {
// Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the
@@ -530,7 +549,6 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
let subclass_input = ThreadMsgTargetSubclassInput {
event_loop_runner,
user_event_receiver: rx,
modifiers_state: ModifiersStateSide::default(),
};
let input_ptr = Box::into_raw(Box::new(subclass_input));
let subclass_result = commctrl::SetWindowSubclass(
@@ -583,6 +601,27 @@ fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
}
}
/// Emit a `ModifiersChanged` event whenever modifiers have changed.
fn update_modifiers<T>(window: HWND, subclass_input: &SubclassInput<T>) {
use crate::event::WindowEvent::ModifiersChanged;
let modifiers = event::get_key_mods();
let mut window_state = subclass_input.window_state.lock();
if window_state.modifiers_state != modifiers {
window_state.modifiers_state = modifiers;
// Drop lock
drop(window_state);
unsafe {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ModifiersChanged(modifiers),
});
}
}
}
/// Any window whose callback is configured to this function will have its events propagated
/// through the events loop of the thread the window was created in.
//
@@ -615,13 +654,6 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
}
winuser::WM_NCLBUTTONDOWN => {
// jumpstart the modal loop
winuser::RedrawWindow(
window,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT,
);
if wparam == winuser::HTCAPTION as _ {
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
}
@@ -707,7 +739,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
let windowpos = lparam as *const winuser::WINDOWPOS;
if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE {
let physical_position =
PhysicalPosition::new((*windowpos).x as u32, (*windowpos).y as u32);
PhysicalPosition::new((*windowpos).x as i32, (*windowpos).y as i32);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: Moved(physical_position),
@@ -829,18 +861,30 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
});
}
let x = windowsx::GET_X_LPARAM(lparam) as i32;
let y = windowsx::GET_Y_LPARAM(lparam) as i32;
let x = windowsx::GET_X_LPARAM(lparam) as f64;
let y = windowsx::GET_Y_LPARAM(lparam) as f64;
let position = PhysicalPosition::new(x, y);
let cursor_moved;
{
// handle spurious WM_MOUSEMOVE messages
// see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343
// and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html
let mut w = subclass_input.window_state.lock();
cursor_moved = w.mouse.last_position != Some(position);
w.mouse.last_position = Some(position);
}
if cursor_moved {
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorMoved {
device_id: DEVICE_ID,
position,
modifiers: event::get_key_mods(),
},
});
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorMoved {
device_id: DEVICE_ID,
position,
modifiers: event::get_key_mods(),
},
});
}
0
}
@@ -871,6 +915,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
let value = value as i32;
let value = value as f32 / winuser::WHEEL_DELTA as f32;
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::MouseWheel {
@@ -891,6 +937,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
let value = value as i32;
let value = value as f32 / winuser::WHEEL_DELTA as f32;
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::MouseWheel {
@@ -910,6 +958,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
commctrl::DefSubclassProc(window, msg, wparam, lparam)
} else {
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
update_modifiers(window, subclass_input);
#[allow(deprecated)]
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
@@ -939,6 +990,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
winuser::WM_KEYUP | winuser::WM_SYSKEYUP => {
use crate::event::ElementState::Released;
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
update_modifiers(window, subclass_input);
#[allow(deprecated)]
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
@@ -961,6 +1015,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
capture_mouse(window, &mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -980,6 +1036,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
release_mouse(&mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -999,6 +1057,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
capture_mouse(window, &mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -1018,6 +1078,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
release_mouse(&mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -1037,6 +1099,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
capture_mouse(window, &mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -1056,6 +1120,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
release_mouse(&mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -1076,6 +1142,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
capture_mouse(window, &mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -1096,6 +1164,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
release_mouse(&mut *subclass_input.window_state.lock());
update_modifiers(window, subclass_input);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: MouseInput {
@@ -1304,6 +1374,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC);
let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode);
update_modifiers(window, subclass_input);
#[allow(deprecated)]
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
@@ -1328,12 +1401,17 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
}
winuser::WM_KILLFOCUS => {
use crate::event::{ElementState::Released, WindowEvent::Focused};
use crate::event::{
ElementState::Released,
ModifiersState,
WindowEvent::{Focused, ModifiersChanged},
};
for windows_keycode in event::get_pressed_keys() {
let scancode =
winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC);
let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode);
#[allow(deprecated)]
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
@@ -1349,6 +1427,12 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
})
}
subclass_input.window_state.lock().modifiers_state = ModifiersState::empty();
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ModifiersChanged(ModifiersState::empty()),
});
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: Focused(false),
@@ -1392,7 +1476,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
if window_state.min_size.is_some() || window_state.max_size.is_some() {
if let Some(min_size) = window_state.min_size {
let min_size = min_size.to_physical(window_state.dpi_factor);
let min_size = min_size.to_physical(window_state.scale_factor);
let (width, height): (u32, u32) = util::adjust_size(window, min_size).into();
(*mmi).ptMinTrackSize = POINT {
x: width as i32,
@@ -1400,7 +1484,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
};
}
if let Some(max_size) = window_state.max_size {
let max_size = max_size.to_physical(window_state.dpi_factor);
let max_size = max_size.to_physical(window_state.scale_factor);
let (width, height): (u32, u32) = util::adjust_size(window, max_size).into();
(*mmi).ptMaxTrackSize = POINT {
x: width as i32,
@@ -1422,15 +1506,15 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
// application since they are the same".
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx
let new_dpi_x = u32::from(LOWORD(wparam as DWORD));
let new_dpi_factor = dpi_to_scale_factor(new_dpi_x);
let old_dpi_factor: f64;
let new_scale_factor = dpi_to_scale_factor(new_dpi_x);
let old_scale_factor: f64;
let allow_resize = {
let mut window_state = subclass_input.window_state.lock();
old_dpi_factor = window_state.dpi_factor;
window_state.dpi_factor = new_dpi_factor;
old_scale_factor = window_state.scale_factor;
window_state.scale_factor = new_scale_factor;
if new_dpi_factor == old_dpi_factor {
if new_scale_factor == old_scale_factor {
return 0;
}
@@ -1440,7 +1524,6 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
let style = winuser::GetWindowLongW(window, winuser::GWL_STYLE) as _;
let style_ex = winuser::GetWindowLongW(window, winuser::GWL_EXSTYLE) as _;
let b_menu = !winuser::GetMenu(window).is_null() as BOOL;
// New size as suggested by Windows.
let suggested_rect = *(lparam as *const RECT);
@@ -1454,14 +1537,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
// let margin_right: i32;
// let margin_bottom: i32;
{
let mut adjusted_rect = suggested_rect;
winuser::AdjustWindowRectExForDpi(
&mut adjusted_rect,
style,
b_menu,
style_ex,
new_dpi_x,
);
let adjusted_rect =
util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect)
.unwrap_or(suggested_rect);
margin_left = suggested_rect.left - adjusted_rect.left;
margin_top = suggested_rect.top - adjusted_rect.top;
// margin_right = adjusted_rect.right - suggested_rect.right;
@@ -1492,15 +1570,15 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
// We calculate our own size because the default suggested rect doesn't do a great job
// of preserving the window's logical size.
true => old_physical_inner_size
.to_logical::<f64>(old_dpi_factor)
.to_physical::<u32>(new_dpi_factor),
.to_logical::<f64>(old_scale_factor)
.to_physical::<u32>(new_scale_factor),
false => old_physical_inner_size,
};
let _ = subclass_input.send_event_unbuffered(Event::WindowEvent {
let _ = subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: ScaleFactorChanged {
scale_factor: new_dpi_factor,
scale_factor: new_scale_factor,
new_inner_size: &mut new_physical_inner_size,
},
});
@@ -1526,13 +1604,13 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
bottom: suggested_ul.1 + new_physical_inner_size.height as LONG,
};
winuser::AdjustWindowRectExForDpi(
&mut conservative_rect,
conservative_rect = util::adjust_window_rect_with_styles(
window,
style,
b_menu,
style_ex,
new_dpi_x,
);
conservative_rect,
)
.unwrap_or(conservative_rect);
// If we're not dragging the window, offset the window so that the cursor's
// relative horizontal position in the title bar is preserved.
@@ -1696,34 +1774,28 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
};
let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop();
if in_modal_loop {
let runner = &subclass_input.event_loop_runner;
runner.main_events_cleared();
// Drain eventual WM_PAINT messages sent if user called request_redraw()
// during handling of MainEventsCleared.
let mut msg = mem::zeroed();
loop {
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
if 0 == winuser::PeekMessageW(
&mut msg,
ptr::null_mut(),
winuser::WM_PAINT,
winuser::WM_PAINT,
winuser::PM_QS_PAINT | winuser::PM_REMOVE,
) {
break;
}
// Clear all paint/timer messages from the queue before sending the events cleared message.
match msg.message {
// Flush the event queue of WM_PAINT messages.
winuser::WM_PAINT | winuser::WM_TIMER => {
// Remove the message from the message queue.
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1);
if msg.hwnd != window {
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
}
}
// If the message isn't one of those three, it may be handled by the modal
// loop so we should return control flow to it.
_ => {
queue_call_again();
return 0;
}
if msg.hwnd != window {
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
}
}
let runner = &subclass_input.event_loop_runner;
runner.events_cleared();
runner.redraw_events_cleared();
match runner.control_flow() {
// Waiting is handled by the modal loop.
ControlFlow::Exit | ControlFlow::Wait => runner.new_events(),
@@ -1758,10 +1830,9 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
winuser::WM_INPUT => {
use crate::event::{
DeviceEvent::{Button, Key, ModifiersChanged, Motion, MouseMotion, MouseWheel},
DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel},
ElementState::{Pressed, Released},
MouseScrollDelta::LineDelta,
VirtualKeyCode,
};
if let Some(data) = raw_input::get_raw_input_data(lparam as _) {
@@ -1840,55 +1911,14 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
{
let virtual_keycode = vkey_to_winit_vkey(vkey);
// If we ever change the DeviceEvent API to only emit events when a
// window is focused, we'll need to emit synthetic `ModifiersChanged`
// events when Winit windows lose focus so that these don't drift out
// of sync with the actual modifier state.
let old_modifiers_state =
subclass_input.modifiers_state.filter_out_altgr().into();
match virtual_keycode {
Some(VirtualKeyCode::LShift) => subclass_input
.modifiers_state
.set(ModifiersStateSide::LSHIFT, pressed),
Some(VirtualKeyCode::RShift) => subclass_input
.modifiers_state
.set(ModifiersStateSide::RSHIFT, pressed),
Some(VirtualKeyCode::LControl) => subclass_input
.modifiers_state
.set(ModifiersStateSide::LCTRL, pressed),
Some(VirtualKeyCode::RControl) => subclass_input
.modifiers_state
.set(ModifiersStateSide::RCTRL, pressed),
Some(VirtualKeyCode::LAlt) => subclass_input
.modifiers_state
.set(ModifiersStateSide::LALT, pressed),
Some(VirtualKeyCode::RAlt) => subclass_input
.modifiers_state
.set(ModifiersStateSide::RALT, pressed),
Some(VirtualKeyCode::LWin) => subclass_input
.modifiers_state
.set(ModifiersStateSide::LLOGO, pressed),
Some(VirtualKeyCode::RWin) => subclass_input
.modifiers_state
.set(ModifiersStateSide::RLOGO, pressed),
_ => (),
}
let new_modifiers_state =
subclass_input.modifiers_state.filter_out_altgr().into();
if new_modifiers_state != old_modifiers_state {
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: ModifiersChanged(new_modifiers_state),
});
}
#[allow(deprecated)]
subclass_input.send_event(Event::DeviceEvent {
device_id,
event: Key(KeyboardInput {
scancode,
state,
virtual_keycode,
modifiers: new_modifiers_state,
modifiers: event::get_key_mods(),
}),
});
}

View File

@@ -3,18 +3,19 @@ use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::R
use winapi::{shared::windef::HWND, um::winuser};
use crate::{
event::{Event, StartCause},
dpi::PhysicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::event_loop::EventLoop,
platform_impl::platform::event_loop::{util, EventLoop},
window::WindowId,
};
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
pub(crate) struct ELRShared<T: 'static> {
runner: RefCell<Option<EventLoopRunner<T>>>,
buffer: RefCell<VecDeque<Event<'static, T>>>,
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
buffer: RefCell<VecDeque<BufferedEvent<T>>>,
}
struct EventLoopRunner<T: 'static> {
control_flow: ControlFlow,
runner_state: RunnerState,
@@ -22,16 +23,56 @@ struct EventLoopRunner<T: 'static> {
in_modal_loop: bool,
event_handler: Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
panic_error: Option<PanicError>,
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
}
pub type PanicError = Box<dyn Any + Send + 'static>;
pub enum BufferedEvent<T: 'static> {
Event(Event<'static, T>),
ScaleFactorChanged(WindowId, f64, PhysicalSize<u32>),
}
impl<T> BufferedEvent<T> {
pub fn from_event(event: Event<'_, T>) -> BufferedEvent<T> {
match event {
Event::WindowEvent {
event:
WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
window_id,
} => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size),
event => BufferedEvent::Event(event.to_static().unwrap()),
}
}
pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) {
match self {
Self::Event(event) => dispatch(event),
Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => {
dispatch(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut new_inner_size,
},
});
util::set_inner_size_physical(
(window_id.0).0,
new_inner_size.width as _,
new_inner_size.height as _,
);
}
}
}
}
impl<T> ELRShared<T> {
pub(crate) fn new() -> ELRShared<T> {
ELRShared {
runner: RefCell::new(None),
buffer: RefCell::new(VecDeque::new()),
redraw_buffer: Default::default(),
}
}
@@ -39,18 +80,11 @@ impl<T> ELRShared<T> {
where
F: FnMut(Event<'_, T>, &mut ControlFlow),
{
let mut runner = EventLoopRunner::new(event_loop, self.redraw_buffer.clone(), f);
let mut runner = EventLoopRunner::new(event_loop, f);
{
let mut runner_ref = self.runner.borrow_mut();
loop {
let event = self.buffer.borrow_mut().pop_front();
match event {
Some(e) => {
runner.process_event(e);
}
None => break,
}
}
// Dispatch any events that were buffered during the creation of the window
self.dispatch_buffered_events(&mut runner);
*runner_ref = Some(runner);
}
}
@@ -63,58 +97,75 @@ impl<T> ELRShared<T> {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.new_events();
// Dispatch any events that were buffered during the call `new_events`
self.dispatch_buffered_events(runner);
}
}
pub(crate) unsafe fn send_event(&self, event: Event<'static, T>) {
pub(crate) fn send_event(&self, event: Event<'_, T>) {
if let Err(event) = self.send_event_unbuffered(event) {
// If the runner is already borrowed, we're in the middle of an event loop invocation. Add
// the event to a buffer to be processed later.
self.buffer_event(event);
// If the runner is already borrowed, we're in the middle of an event loop invocation.
// Add the event to a buffer to be processed later.
if let Event::RedrawRequested(_) = event {
panic!("buffering RedrawRequested event");
}
self.buffer
.borrow_mut()
.push_back(BufferedEvent::from_event(event));
}
}
pub(crate) unsafe fn send_event_unbuffered<'e>(
&self,
event: Event<'e, T>,
) -> Result<(), Event<'e, T>> {
fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> {
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
if let Some(ref mut runner) = *runner_ref {
runner.process_event(event);
// Dispatch any events that were buffered during the call to `process_event`.
loop {
// We do this instead of using a `while let` loop because if we use a `while let`
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
// of the loop's body and attempts to add events to the event buffer while in
// `process_event` will fail.
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
match buffered_event_opt {
Some(event) => runner.process_event(event),
None => break,
}
}
self.dispatch_buffered_events(runner);
return Ok(());
}
}
Err(event)
}
pub(crate) unsafe fn call_event_handler(&self, event: Event<'static, T>) {
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
if let Some(ref mut runner) = *runner_ref {
runner.call_event_handler(event);
return;
fn dispatch_buffered_events(&self, runner: &mut EventLoopRunner<T>) {
// We do this instead of using a `while let` loop because if we use a `while let`
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
// of the loop's body and attempts to add events to the event buffer while in
// `process_event` will fail.
loop {
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
match buffered_event_opt {
Some(e) => e.dispatch_event(|e| runner.process_event(e)),
None => break,
}
}
}
pub(crate) fn events_cleared(&self) {
pub(crate) fn main_events_cleared(&self) {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.events_cleared();
runner.main_events_cleared();
if !self.buffer.borrow().is_empty() {
warn!("Buffered events while dispatching MainEventsCleared");
}
}
}
pub(crate) fn redraw_events_cleared(&self) {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.redraw_events_cleared();
if !self.buffer.borrow().is_empty() {
warn!("Buffered events while dispatching RedrawEventsCleared");
}
}
}
pub(crate) fn destroy_loop(&self) {
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
if let Some(ref mut runner) = *runner_ref {
runner.call_event_handler(Event::LoopDestroyed);
}
}
}
@@ -131,6 +182,17 @@ impl<T> ELRShared<T> {
let mut runner_ref = self.runner.borrow_mut();
if let Some(ref mut runner) = *runner_ref {
runner.in_modal_loop = in_modal_loop;
if in_modal_loop {
// jumpstart the modal loop
unsafe {
winuser::RedrawWindow(
runner.modal_redraw_window,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT,
);
}
}
}
}
@@ -151,15 +213,6 @@ impl<T> ELRShared<T> {
ControlFlow::Exit
}
}
fn buffer_event(&self, event: Event<'static, T>) {
match event {
Event::RedrawRequested(window_id) => {
self.redraw_buffer.borrow_mut().push_back(window_id)
}
_ => self.buffer.borrow_mut().push_back(event),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -173,17 +226,15 @@ enum RunnerState {
/// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`.
DeferredNewEvents(Instant),
/// The event loop is handling the OS's events and sending them to the user's callback.
/// `NewEvents` has been sent, and `EventsCleared` hasn't.
/// `NewEvents` has been sent, and `MainEventsCleared` hasn't.
HandlingEvents,
/// The event loop is handling the redraw events and sending them to the user's callback.
/// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't.
HandlingRedraw,
}
impl<T> EventLoopRunner<T> {
unsafe fn new<F>(
event_loop: &EventLoop<T>,
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
f: F,
) -> EventLoopRunner<T>
unsafe fn new<F>(event_loop: &EventLoop<T>, f: F) -> EventLoopRunner<T>
where
F: FnMut(Event<'_, T>, &mut ControlFlow),
{
@@ -197,7 +248,6 @@ impl<T> EventLoopRunner<T> {
Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
>(Box::new(f)),
panic_error: None,
redraw_buffer,
}
}
@@ -223,6 +273,8 @@ impl<T> EventLoopRunner<T> {
}
// When `NewEvents` gets sent after an idle depends on the control flow...
// Some `NewEvents` are deferred because not all Windows messages trigger an event_loop event.
// So we defer the `NewEvents` to when we actually process an event.
RunnerState::Idle(wait_start) => {
match self.control_flow {
// If we're polling, send `NewEvents` and immediately move into event processing.
@@ -308,25 +360,26 @@ impl<T> EventLoopRunner<T> {
self.call_event_handler(Event::NewEvents(start_cause));
}
// This can be reached if the control flow is changed to poll during a `RedrawRequested`
// that was sent after `EventsCleared`.
// that was sent after `MainEventsCleared`.
ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)),
}
self.runner_state = RunnerState::HandlingEvents;
}
match (self.runner_state, &event) {
(RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => {
self.call_event_handler(event)
}
(_, Event::RedrawRequested(_)) => {
(RunnerState::HandlingEvents, Event::RedrawRequested(window_id)) => {
self.call_event_handler(Event::MainEventsCleared);
self.runner_state = RunnerState::HandlingRedraw;
self.call_event_handler(event);
self.call_event_handler(Event::RedrawRequested(*window_id));
}
(RunnerState::HandlingRedraw, Event::RedrawRequested(window_id)) => {
self.call_event_handler(Event::RedrawRequested(*window_id));
}
(RunnerState::HandlingRedraw, _) => {
warn!("Non-redraw event dispatched durning redraw phase");
self.events_cleared();
self.new_events();
self.call_event_handler(event);
warn!(
"non-redraw event in redraw phase: {:?}",
event.map_nonuser_event::<()>().ok()
);
}
(_, _) => {
self.runner_state = RunnerState::HandlingEvents;
@@ -335,30 +388,17 @@ impl<T> EventLoopRunner<T> {
}
}
fn flush_redraws(&mut self) {
loop {
let redraw_window_opt = self.redraw_buffer.borrow_mut().pop_front();
match redraw_window_opt {
Some(window_id) => self.process_event(Event::RedrawRequested(window_id)),
None => break,
}
}
}
fn events_cleared(&mut self) {
fn main_events_cleared(&mut self) {
match self.runner_state {
// If we were handling events, send the EventsCleared message.
// If we were handling events, send the MainEventsCleared message.
RunnerState::HandlingEvents => {
self.call_event_handler(Event::MainEventsCleared);
self.flush_redraws();
self.call_event_handler(Event::RedrawEventsCleared);
self.runner_state = RunnerState::Idle(Instant::now());
self.runner_state = RunnerState::HandlingRedraw;
}
RunnerState::HandlingRedraw => {
self.call_event_handler(Event::RedrawEventsCleared);
self.runner_state = RunnerState::Idle(Instant::now());
}
// We already cleared the main events, we don't have to do anything.
// This happens when process_events() processed a RedrawRequested event.
RunnerState::HandlingRedraw => {}
// If we *weren't* handling events, we don't have to do anything.
RunnerState::New | RunnerState::Idle(..) => (),
@@ -367,15 +407,15 @@ impl<T> EventLoopRunner<T> {
// branch handles those.
RunnerState::DeferredNewEvents(wait_start) => {
match self.control_flow {
// If we had deferred a Poll, send the Poll NewEvents and EventsCleared.
// If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared.
ControlFlow::Poll => {
self.call_event_handler(Event::NewEvents(StartCause::Poll));
self.runner_state = RunnerState::HandlingEvents;
self.call_event_handler(Event::MainEventsCleared);
self.flush_redraws();
self.call_event_handler(Event::RedrawEventsCleared);
self.runner_state = RunnerState::HandlingRedraw;
}
// If we had deferred a WaitUntil and the resume time has since been reached,
// send the resume notification and EventsCleared event.
// send the resume notification and MainEventsCleared event.
ControlFlow::WaitUntil(resume_time) => {
if Instant::now() >= resume_time {
self.call_event_handler(Event::NewEvents(
@@ -384,21 +424,36 @@ impl<T> EventLoopRunner<T> {
requested_resume: resume_time,
},
));
self.runner_state = RunnerState::HandlingEvents;
self.call_event_handler(Event::MainEventsCleared);
self.flush_redraws();
self.call_event_handler(Event::RedrawEventsCleared);
self.runner_state = RunnerState::HandlingRedraw;
}
}
// If we deferred a wait and no events were received, the user doesn't have to
// get an event.
ControlFlow::Wait | ControlFlow::Exit => (),
}
// Mark that we've entered an idle state.
self.runner_state = RunnerState::Idle(wait_start)
}
}
}
fn redraw_events_cleared(&mut self) {
match self.runner_state {
// If we were handling redraws, send the RedrawEventsCleared message.
RunnerState::HandlingRedraw => {
self.call_event_handler(Event::RedrawEventsCleared);
self.runner_state = RunnerState::Idle(Instant::now());
}
// No event was processed, we don't have to do anything.
RunnerState::DeferredNewEvents(_) => (),
// Should not happen.
_ => warn!(
"unexpected state in redraw_events_cleared: {:?}",
self.runner_state
),
}
}
fn call_event_handler(&mut self, event: Event<'_, T>) {
if self.panic_error.is_none() {
let EventLoopRunner {

View File

@@ -1,15 +1,17 @@
use std::{io, mem, os::windows::ffi::OsStrExt, path::Path, ptr};
use std::{fmt, io, iter::once, mem, os::windows::ffi::OsStrExt, path::Path, ptr, sync::Arc};
use winapi::{
ctypes::{c_int, wchar_t},
shared::{
minwindef::{BYTE, LPARAM, WPARAM},
minwindef::{BYTE, LPARAM, WORD, WPARAM},
windef::{HICON, HWND},
},
um::libloaderapi,
um::winuser,
};
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
use crate::dpi::PhysicalSize;
use crate::icon::*;
impl Pixel {
fn to_bgra(&mut self) {
@@ -17,92 +19,149 @@ impl Pixel {
}
}
impl RgbaIcon {
fn into_windows_icon(self) -> Result<WinIcon, BadIcon> {
let mut rgba = self.rgba;
let pixel_count = rgba.len() / PIXEL_SIZE;
let mut and_mask = Vec::with_capacity(pixel_count);
let pixels =
unsafe { std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count) };
for pixel in pixels {
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
pixel.to_bgra();
}
assert_eq!(and_mask.len(), pixel_count);
let handle = unsafe {
winuser::CreateIcon(
ptr::null_mut(),
self.width as c_int,
self.height as c_int,
1,
(PIXEL_SIZE * 8) as BYTE,
and_mask.as_ptr() as *const BYTE,
rgba.as_ptr() as *const BYTE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon::from_handle(handle))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
}
}
}
#[derive(Debug)]
pub enum IconType {
Small = winuser::ICON_SMALL as isize,
Big = winuser::ICON_BIG as isize,
}
#[derive(Clone, Debug)]
#[derive(Debug)]
struct RaiiIcon {
handle: HICON,
}
#[derive(Clone)]
pub struct WinIcon {
pub handle: HICON,
inner: Arc<RaiiIcon>,
}
unsafe impl Send for WinIcon {}
impl WinIcon {
#[allow(dead_code)]
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let wide_path: Vec<u16> = path.as_ref().as_os_str().encode_wide().collect();
pub fn as_raw_handle(&self) -> HICON {
self.inner.handle
}
pub fn from_path<P: AsRef<Path>>(
path: P,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
let wide_path: Vec<u16> = path
.as_ref()
.as_os_str()
.encode_wide()
.chain(once(0))
.collect();
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
let handle = unsafe {
winuser::LoadImageW(
ptr::null_mut(),
wide_path.as_ptr() as *const wchar_t,
winuser::IMAGE_ICON,
0, // 0 indicates that we want to use the actual width
0, // and height
winuser::LR_LOADFROMFILE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon { handle })
} else {
Err(io::Error::last_os_error())
}
}
pub fn from_icon(icon: Icon) -> Result<Self, io::Error> {
Self::from_rgba(icon.rgba, icon.width, icon.height)
}
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, io::Error> {
assert_eq!(rgba.len() % PIXEL_SIZE, 0);
let pixel_count = rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (width * height) as usize);
let mut and_mask = Vec::with_capacity(pixel_count);
let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust
for pixel_index in 0..pixel_count {
let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) };
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
pixel.to_bgra();
}
assert_eq!(and_mask.len(), pixel_count);
let handle = unsafe {
winuser::CreateIcon(
ptr::null_mut(),
width as c_int,
height as c_int,
1,
(PIXEL_SIZE * 8) as BYTE,
and_mask.as_ptr() as *const BYTE,
rgba.as_ptr() as *const BYTE,
winuser::LR_DEFAULTSIZE | winuser::LR_LOADFROMFILE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon { handle })
Ok(WinIcon::from_handle(handle))
} else {
Err(io::Error::last_os_error())
Err(BadIcon::OsError(io::Error::last_os_error()))
}
}
pub fn from_resource(
resource_id: WORD,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
let handle = unsafe {
winuser::LoadImageW(
libloaderapi::GetModuleHandleW(ptr::null_mut()),
winuser::MAKEINTRESOURCEW(resource_id),
winuser::IMAGE_ICON,
width as c_int,
height as c_int,
winuser::LR_DEFAULTSIZE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon::from_handle(handle))
} else {
Err(BadIcon::OsError(io::Error::last_os_error()))
}
}
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?;
rgba_icon.into_windows_icon()
}
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
unsafe {
winuser::SendMessageW(
hwnd,
winuser::WM_SETICON,
icon_type as WPARAM,
self.handle as LPARAM,
self.as_raw_handle() as LPARAM,
);
}
}
fn from_handle(handle: HICON) -> Self {
Self {
inner: Arc::new(RaiiIcon { handle }),
}
}
}
impl Drop for WinIcon {
impl Drop for RaiiIcon {
fn drop(&mut self) {
unsafe { winuser::DestroyIcon(self.handle) };
}
}
impl fmt::Debug for WinIcon {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
(*self.inner).fmt(formatter)
}
}
pub fn unset_for_window(hwnd: HWND, icon_type: IconType) {
unsafe {
winuser::SendMessageW(hwnd, winuser::WM_SETICON, icon_type as WPARAM, 0 as LPARAM);

View File

@@ -4,11 +4,15 @@ use winapi::{self, shared::windef::HWND};
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
icon::WinIcon,
monitor::{MonitorHandle, VideoMode},
window::Window,
};
use crate::{event::DeviceId as RootDeviceId, window::Icon};
pub use self::icon::WinIcon as PlatformIcon;
use crate::event::DeviceId as RootDeviceId;
use crate::icon::Icon;
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {

View File

@@ -88,6 +88,38 @@ pub fn adjust_size(hwnd: HWND, size: PhysicalSize<u32>) -> PhysicalSize<u32> {
PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _)
}
pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) {
unsafe {
let rect = adjust_window_rect(
window,
RECT {
top: 0,
left: 0,
bottom: y as LONG,
right: x as LONG,
},
)
.expect("adjust_window_rect failed");
let outer_x = (rect.right - rect.left).abs() as _;
let outer_y = (rect.top - rect.bottom).abs() as _;
winuser::SetWindowPos(
window,
ptr::null_mut(),
0,
0,
outer_x,
outer_y,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOREPOSITION
| winuser::SWP_NOMOVE
| winuser::SWP_NOACTIVATE,
);
winuser::InvalidateRgn(window, ptr::null_mut(), 0);
}
}
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
unsafe {
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);

View File

@@ -24,7 +24,7 @@ use winapi::{
oleidl::LPDROPTARGET,
shobjidl_core::{CLSID_TaskbarList, ITaskbarList2},
wingdi::{CreateRectRgn, DeleteObject},
winnt::{LONG, LPCWSTR},
winnt::LPCWSTR,
winuser,
},
};
@@ -32,18 +32,19 @@ use winapi::{
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{
dark_mode::try_dark_mode,
dpi::{dpi_to_scale_factor, hwnd_dpi},
drop_handler::FileDropHandler,
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID},
icon::{self, IconType, WinIcon},
icon::{self, IconType},
monitor, util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId,
},
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
/// The Win32 implementation of the main `Window` object.
@@ -187,7 +188,7 @@ impl Window {
| winuser::SWP_NOSIZE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(self.window.0);
winuser::InvalidateRgn(self.window.0, ptr::null_mut(), 0);
}
}
@@ -215,42 +216,10 @@ impl Window {
.unwrap()
}
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
unsafe {
let rect = util::adjust_window_rect(
self.window.0,
RECT {
top: 0,
left: 0,
bottom: y as LONG,
right: x as LONG,
},
)
.expect("adjust_window_rect failed");
let outer_x = (rect.right - rect.left).abs() as c_int;
let outer_y = (rect.top - rect.bottom).abs() as c_int;
winuser::SetWindowPos(
self.window.0,
ptr::null_mut(),
0,
0,
outer_x,
outer_y,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOREPOSITION
| winuser::SWP_NOMOVE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(self.window.0);
}
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
let dpi_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(dpi_factor).into();
let scale_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(scale_factor).into();
let window_state = Arc::clone(&self.window_state);
let window = self.window.clone();
@@ -260,7 +229,7 @@ impl Window {
});
});
self.set_inner_size_physical(width, height);
util::set_inner_size_physical(self.window.0, width, height);
}
#[inline]
@@ -357,13 +326,13 @@ impl Window {
#[inline]
pub fn scale_factor(&self) -> f64 {
self.window_state.lock().dpi_factor
self.window_state.lock().scale_factor
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
let dpi_factor = self.scale_factor();
let (x, y) = position.to_physical::<i32>(dpi_factor).into();
let scale_factor = self.scale_factor();
let (x, y) = position.to_physical::<i32>(scale_factor).into();
let mut point = POINT { x, y };
unsafe {
@@ -434,7 +403,7 @@ impl Window {
let client_rect = util::get_client_rect(window.0).unwrap();
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
dpi_factor: window_state_lock.dpi_factor,
scale_factor: window_state_lock.scale_factor,
});
}
_ => (),
@@ -538,17 +507,17 @@ impl Window {
size.1 as i32,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
);
winuser::UpdateWindow(window.0);
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
}
}
None => {
let mut window_state_lock = window_state.lock();
if let Some(SavedWindow {
client_rect,
dpi_factor,
scale_factor,
}) = window_state_lock.saved_window.take()
{
window_state_lock.dpi_factor = dpi_factor;
window_state_lock.scale_factor = scale_factor;
drop(window_state_lock);
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
@@ -564,7 +533,7 @@ impl Window {
| winuser::SWP_NOZORDER
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(window.0);
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
}
}
}
@@ -608,12 +577,11 @@ impl Window {
}
#[inline]
pub fn set_window_icon(&self, mut window_icon: Option<Icon>) {
let window_icon = window_icon
.take()
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`"));
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
if let Some(ref window_icon) = window_icon {
window_icon.set_for_window(self.window.0, IconType::Small);
window_icon
.inner
.set_for_window(self.window.0, IconType::Small);
} else {
icon::unset_for_window(self.window.0, IconType::Small);
}
@@ -621,12 +589,11 @@ impl Window {
}
#[inline]
pub fn set_taskbar_icon(&self, mut taskbar_icon: Option<Icon>) {
let taskbar_icon = taskbar_icon
.take()
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`"));
pub fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
if let Some(ref taskbar_icon) = taskbar_icon {
taskbar_icon.set_for_window(self.window.0, IconType::Big);
taskbar_icon
.inner
.set_for_window(self.window.0, IconType::Big);
} else {
icon::unset_for_window(self.window.0, IconType::Big);
}
@@ -668,7 +635,7 @@ unsafe impl Sync for WindowWrapper {}
unsafe impl Send for WindowWrapper {}
unsafe fn init<T: 'static>(
mut attributes: WindowAttributes,
attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
event_loop: &EventLoopWindowTarget<T>,
) -> Result<Window, RootOsError> {
@@ -677,25 +644,8 @@ unsafe fn init<T: 'static>(
.chain(Some(0).into_iter())
.collect::<Vec<_>>();
let window_icon = {
let icon = attributes.window_icon.take().map(WinIcon::from_icon);
if let Some(icon) = icon {
Some(icon.map_err(|e| os_error!(e))?)
} else {
None
}
};
let taskbar_icon = {
let icon = attributes.window_icon.take().map(WinIcon::from_icon);
if let Some(icon) = icon {
Some(icon.map_err(|e| os_error!(e))?)
} else {
None
}
};
// registering the window class
let class_name = register_window_class(&window_icon, &taskbar_icon);
let class_name = register_window_class(&attributes.window_icon, &pl_attribs.taskbar_icon);
let mut window_flags = WindowFlags::empty();
window_flags.set(WindowFlags::DECORATIONS, attributes.decorations);
@@ -744,7 +694,7 @@ unsafe fn init<T: 'static>(
}
let dpi = hwnd_dpi(real_window.0);
let dpi_factor = dpi_to_scale_factor(dpi);
let scale_factor = dpi_to_scale_factor(dpi);
// making the window transparent
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
@@ -788,9 +738,8 @@ unsafe fn init<T: 'static>(
let window_state = {
let window_state = WindowState::new(
&attributes,
window_icon,
taskbar_icon,
dpi_factor,
pl_attribs.taskbar_icon,
scale_factor,
dark_mode,
);
let window_state = Arc::new(Mutex::new(window_state));
@@ -819,8 +768,8 @@ unsafe fn init<T: 'static>(
}
unsafe fn register_window_class(
window_icon: &Option<WinIcon>,
taskbar_icon: &Option<WinIcon>,
window_icon: &Option<Icon>,
taskbar_icon: &Option<Icon>,
) -> Vec<u16> {
let class_name: Vec<_> = OsStr::new("Window Class")
.encode_wide()
@@ -829,11 +778,11 @@ unsafe fn register_window_class(
let h_icon = taskbar_icon
.as_ref()
.map(|icon| icon.handle)
.map(|icon| icon.inner.as_raw_handle())
.unwrap_or(ptr::null_mut());
let h_icon_small = window_icon
.as_ref()
.map(|icon| icon.handle)
.map(|icon| icon.inner.as_raw_handle())
.unwrap_or(ptr::null_mut());
let class = winuser::WNDCLASSEXW {

View File

@@ -1,6 +1,8 @@
use crate::{
dpi::Size,
platform_impl::platform::{event_loop, icon::WinIcon, util},
dpi::{PhysicalPosition, Size},
event::ModifiersState,
icon::Icon,
platform_impl::platform::{event_loop, util},
window::{CursorIcon, Fullscreen, WindowAttributes},
};
use parking_lot::MutexGuard;
@@ -14,7 +16,6 @@ use winapi::{
};
/// Contains information about states and the window that the callback is going to use.
#[derive(Clone)]
pub struct WindowState {
pub mouse: MouseProperties,
@@ -22,16 +23,14 @@ pub struct WindowState {
pub min_size: Option<Size>,
pub max_size: Option<Size>,
pub window_icon: Option<WinIcon>,
pub taskbar_icon: Option<WinIcon>,
pub window_icon: Option<Icon>,
pub taskbar_icon: Option<Icon>,
pub saved_window: Option<SavedWindow>,
pub dpi_factor: f64,
pub scale_factor: f64,
pub modifiers_state: ModifiersState,
pub fullscreen: Option<Fullscreen>,
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
/// times in `EventsCleared`.
pub queued_out_of_band_redraw: bool,
pub is_dark_mode: bool,
pub high_surrogate: Option<u16>,
window_flags: WindowFlags,
@@ -40,7 +39,7 @@ pub struct WindowState {
#[derive(Clone)]
pub struct SavedWindow {
pub client_rect: RECT,
pub dpi_factor: f64,
pub scale_factor: f64,
}
#[derive(Clone)]
@@ -48,6 +47,7 @@ pub struct MouseProperties {
pub cursor: CursorIcon,
pub buttons_down: u32,
cursor_flags: CursorFlags,
pub last_position: Option<PhysicalPosition<f64>>,
}
bitflags! {
@@ -96,9 +96,8 @@ bitflags! {
impl WindowState {
pub fn new(
attributes: &WindowAttributes,
window_icon: Option<WinIcon>,
taskbar_icon: Option<WinIcon>,
dpi_factor: f64,
taskbar_icon: Option<Icon>,
scale_factor: f64,
is_dark_mode: bool,
) -> WindowState {
WindowState {
@@ -106,19 +105,20 @@ impl WindowState {
cursor: CursorIcon::default(),
buttons_down: 0,
cursor_flags: CursorFlags::empty(),
last_position: None,
},
min_size: attributes.min_inner_size,
max_size: attributes.max_inner_size,
window_icon,
window_icon: attributes.window_icon.clone(),
taskbar_icon,
saved_window: None,
dpi_factor,
scale_factor,
modifiers_state: ModifiersState::default(),
fullscreen: None,
queued_out_of_band_redraw: false,
is_dark_mode,
high_surrogate: None,
window_flags: WindowFlags::empty(),
@@ -268,7 +268,7 @@ impl WindowFlags {
| winuser::SWP_NOSIZE
| winuser::SWP_NOACTIVATE,
);
winuser::UpdateWindow(window);
winuser::InvalidateRgn(window, ptr::null_mut(), 0);
}
}

View File

@@ -9,7 +9,7 @@ use crate::{
platform_impl,
};
pub use crate::icon::*;
pub use crate::icon::{BadIcon, Icon};
/// Represents a window.
///
@@ -26,12 +26,14 @@ pub use crate::icon::*;
/// let window = Window::new(&event_loop).unwrap();
///
/// event_loop.run(move |event, _, control_flow| {
/// *control_flow = ControlFlow::Wait;
///
/// match event {
/// Event::WindowEvent {
/// event: WindowEvent::CloseRequested,
/// ..
/// } => *control_flow = ControlFlow::Exit,
/// _ => *control_flow = ControlFlow::Wait,
/// _ => (),
/// }
/// });
/// ```
@@ -78,7 +80,7 @@ impl WindowId {
}
/// Object that allows you to build windows.
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct WindowBuilder {
/// The attributes to use to create the window.
pub window: WindowAttributes,
@@ -185,10 +187,7 @@ impl WindowBuilder {
/// Initializes a new `WindowBuilder` with default values.
#[inline]
pub fn new() -> Self {
WindowBuilder {
window: Default::default(),
platform_specific: Default::default(),
}
Default::default()
}
/// Requests the window to be of specific dimensions.
@@ -393,10 +392,10 @@ impl Window {
/// This is the **strongly encouraged** method of redrawing windows, as it can integrate with
/// OS-requested redraws (e.g. when a window gets resized).
///
/// This function can cause `RedrawRequested` events to be emitted after `Event::EventsCleared`
/// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared`
/// but before `Event::NewEvents` if called in the following circumstances:
/// * While processing `EventsCleared`.
/// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any
/// * While processing `MainEventsCleared`.
/// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any
/// directly subsequent `RedrawRequested` event.
///
/// ## Platform-specific