Compare commits

..

125 Commits

Author SHA1 Message Date
Kirill Chibisov
911fad0af0 Winit version 0.30.11 2025-05-21 17:50:00 +09:00
Kirill Chibisov
2191eacfc8 chore: appease clippy 2025-05-21 17:50:00 +09:00
Kirill Chibisov
f7ac8127e3 wayland: fix pump events's loop drop deadlock 2025-05-21 17:50:00 +09:00
Varphone Wong
bd2b5cda8d windows: Fix crash in for Windows versions < 17763
In Windows versions < 17763, `GetProcAddress("#132")` from `uxtheme.dll`
also returns a non-null pointer. However, the function does not match
the expected `extern "system" fn() -> bool` prototype, which causes a
crash when it is called.

This fix ensures compatibility by adding proper checks to prevent such
crashes on older Windows versions.
2025-05-21 17:50:00 +09:00
Kirill Chibisov
3930a6334f ci/deny: allow scripts in zerocopy 2025-05-21 17:50:00 +09:00
Bruce Mitchener
17b5737972 Fix typos from updated typos tool (#4213) 2025-05-21 17:50:00 +09:00
Kirill Chibisov
f49a2a1827 clippy: fix casing in windows backend 2025-05-21 17:50:00 +09:00
Kirill Chibisov
2385410366 Winit version 0.30.10 2025-04-30 20:46:44 +09:00
Kirill Chibisov
6db1343c0b wayland: bump wayland-rs to avoid yanked release 2025-04-30 20:46:44 +09:00
Mitoma Ryo
a26899a75d windows: fix incorrect cursor_range calculation in Ime::Preedit
The `text` is retrieved as UTF-8 while `attributes` are based on UTF-16,
thus the offset was getting out of sync on some unicode payloads
like surrogate pairs.

Fixes #3967.
2025-04-30 20:46:44 +09:00
Mads Marquart
80bddda641 macOS: Fix monitors connected via certain Thunderbolt hubs
Instead of panicking, raise a warning and return `None` or similar.

Co-Authored-By: RJ <rj@metabrew.com>
2025-04-30 20:46:44 +09:00
Mads Marquart
5f1e9f6cc1 macOS: Store UUID in MonitorHandle instead of CGDirectDisplayID
The monitor UUID is what actually represents the monitor,
CGDirectDisplayID is closer in correspondence to a specific framebuffer.
2025-04-30 20:46:44 +09:00
Mads Marquart
57baf72741 Allow the user to register the application delegate on iOS
iOS parts of #3758.

This allows the user to override the application delegate themselves,
which opens several doors for customization that were previously closed.
To do this, we use notifications instead of a top-level application
delegate.

One effect of not providing an application delegate on iOS is that we no
longer act as-if the application successfully open all URLs there.

This is a breaking change, although unlikely to matter in practice,
since the return value of `application:didFinishLaunchingWithOptions:`
is seldom used by the system (and this is likely the preferred behaviour
anyhow).
2025-04-30 20:46:44 +09:00
Mads Marquart
da7a09658a fix: Support fractional refresh rates in video modes on macOS (#4191)
We were rounding the refresh rate before converting it to millihertz.
2025-04-30 20:46:44 +09:00
Mads Marquart
53321dc6f5 Swizzle sendEvent: instead of subclassing NSApplication
This is done to avoid order-dependent behavior that you'd otherwise
encounter where `EventLoop::new` had to be called at the beginning of
`fn main` to ensure that Winit's application was the one being
registered as the main application by calling `sharedApplication`.

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

This should also make it (more) possible to use multiple versions of
Winit in the same application (though that's still untested).

Finally, it should allow the user to override `NSApplication` themselves
if they need to do that for some reason.
2025-04-30 20:46:44 +09:00
Mads Marquart
6556cde246 macOS: Close windows automatically when exiting
This disallows carrying over open windows between calls of
`run_app_on_demand` (which wasn't intended to be supported anyhow).
2025-04-30 20:46:44 +09:00
jpy794
7672fd5657 wayland: support fractional scale for custom cursor 2025-04-30 20:46:44 +09:00
Putta Khunchalee
847511672a wayland: add WindowExtWayland::xdg_toplevel
Fixes #4068.
2025-04-30 20:46:44 +09:00
Kirill Chibisov
53bbe6c273 wayland: ensure external loop is notified with pump_events
Spawn a thread when pump_events is used, so the external thread will
get woken-up correctly. This only happens when timeout was given.

Fixes #4183.
2025-04-30 20:46:44 +09:00
robtfm
a224b3de06 windows: add locked cursor 2025-04-30 20:46:44 +09:00
Kirill Chibisov
114599c2da wayland/fix: crash due consequent calls to set_cursor_grab
Only mark that the grab was applied when it actually got applied.
Previously there was an issue with grab being marked as applied without
a pointer over the window, when in reality it wasn't.

Fixes #4073.
2025-04-30 20:46:44 +09:00
Kirill Chibisov
aaecc92b62 chore: fix clippy issues 2025-04-30 20:46:44 +09:00
Kirill Chibisov
c6cfa048b0 x11:wayland: fix pump_events blocking with Wait
Using `Duration::Zero` with `Wait` polling mode was still blocking until
the event was actually delivered. Thus when `pump_events` API is used,
ensure that it's not happening.

Fixes #4130.
2025-04-30 20:46:44 +09:00
aloucks
c591089ece macOS: Make set_simple_fullscreen honor set_borderless_game (#4164)
* Prevent panic when calling set_simple_fullscreen(false) on macos

Calling `set_simple_fullscreen(false)` to restore the window after
a previous call to `set_simple_fullscreen(true)` panics with
`view must be installed in a window` in the call to `set_style_mask`
with the old style.

Moving the `set_style_mask` call after the frame has been resized
fixes the issue.

* Hide the doc and menubar on macos when using set_borderless_game
with set_simple_fullscreen
2025-04-30 20:46:44 +09:00
aloucks
ec7677d692 Fix a pause in the event loop when clicking the title bar on windows (#4136)
* Fix a pause in the event loop when clicking the title bar on windows

When clicking the title bar on Windows, to drag the window, there is
a noticible pause in continuous redraw requests. This was fixed
in #839 and then regressed in #1852. The cursor blinks in both
cases and is unrelated. The regression made the blink happen after
the pause instead of immediately.

* Update the event loop pause note on the WM_NCLBUTTONDOWN handler

The application example was also updated to optionally animate the fill color
in order to demonstrate continuous redraw without pauses in the event
loop.
2025-04-30 20:46:44 +09:00
rctlmk
b9b2f1643e Windows: add IconExtWindows::from_resource_name (#4137) 2025-04-30 20:46:44 +09:00
Kirill Chibisov
1db15b6875 chore: fix clippy lints 2025-04-30 20:46:44 +09:00
Mads Marquart
37a4394a3e Remove apple/appkit/window.rs
Accidentally added in 485ae90aae.
2025-03-17 23:50:15 +01:00
Kirill Chibisov
1ae4f5cdea Winit version 0.30.9 2025-02-06 13:13:19 +03:00
Mads Marquart
501d9b4a44 ios: fix timers
Fixes #4074.
Fixes #3816.
2025-02-06 13:13:19 +03:00
Kirill Chibisov
487137b867 x11: fix modifiers replay
The serial was not unique, thus leading to issues and replay being
triggered for normal input. Track modifiers based on they keycodes
instead, since it's more unique.

Links: https://github.com/alacritty/alacritty/issues/8461
2025-02-06 13:13:19 +03:00
Kirill Chibisov
b77ea7d218 x11: fix crash with uim
Let's just not forward events to the IME once the user requested that
it should be disabled, though, still try to change its state explicitly.

Fixes #4082.
2025-02-06 13:13:19 +03:00
Mads Marquart
3154c60ef4 Document that we require cargo +nightly fmt (#4105) 2025-02-06 13:13:19 +03:00
Tom Churchman
abfe90bddb wayland: clear IME preedit only when necessary
When all we'll be doing is setting a new preedit, the preedit doesn't
have to be explicitly cleared first. This change is perhaps debatable.

The direct reason for this is to make it easier to work around
quirks/bugs: in Masonry we've found IBus appears to resend
the IME preedit in response to `Window::set_ime_cursor_area`
(`zwp_text_input_v3::set_cursor_rectangle`). Because currently the
preedit is first cleared, a new IME cursor area is sent, which again
causes IBus to resend the preedit. This can loop for a while.

The Wayland protocol is mechanically quite prescriptive,
it says for zwp_text_input_v3:event:done.

> 1. Replace existing preedit string with the cursor.
> 2. Delete requested surrounding text.
> 3. Insert commit string with the cursor at its end.
> 4. Calculate surrounding text to send.
> 5. Insert new preedit text in cursor position.
> 6. Place cursor inside preedit text.

Winit currently doesn't do surrounding text, so 2. and 4. can be
ignored. In Winit's IME model, without a commit, sending just the
`Ime::Preedit` event without explicitly clearing is arguably still
equivalent to doing 1., 5., and 6.
2025-02-06 13:13:19 +03:00
Pascal Hertleif
090498a4a6 Use wrapper type for CFUUID (#4032)
This no longer exposes `CGDisplayCreateUUIDFromDisplayID` and instead
uses `CFUUID` to avoid a leak.

Monitor comparisons should also be more stable now.
2025-02-06 13:13:19 +03:00
Kirill Chibisov
58402b58cf Winit version 0.30.8 2025-01-04 08:45:12 +03:00
Kirill Chibisov
d7710f7264 api: add ActivationToken::{from,into}_raw
This is needed when passing and getting token from the IPC to activate
the window.
2025-01-04 08:45:12 +03:00
Kirill Chibisov
61314cd50a x11: fix cursor grab mode tracking on error
Fixes #4064.
2025-01-04 08:45:12 +03:00
Kirill Chibisov
6b5cc165dd x11: add workaround for disabling IME on gnome
GNOME doesn't list that there's a _NONE_ style at all, but it still
works if you use it.
2025-01-04 08:45:12 +03:00
Matt Campbell
43c323ccc0 windows: fix the event loop not waking on accessibility requests
Fixes #4055.
2025-01-04 08:45:12 +03:00
Kirill Chibisov
9cbce055d3 Winit version 0.30.7 2024-12-22 23:00:41 +03:00
Kirill Chibisov
727583ffbf x11: fix KeyboardInput delivered twice with IME
The filtered events were still processed even though they shouldn't once
we know that they're filtered.

Fixes #4048.
2024-12-22 23:00:41 +03:00
Kirill Chibisov
82f0949aea Winit version 0.30.6 2024-12-21 09:05:36 +03:00
Kirill Chibisov
5d2151e893 chore: fix typos 2024-12-21 09:05:36 +03:00
daxpedda
6f22822025 Update minimum version of wasm-bindgen (#3860) 2024-12-21 09:05:36 +03:00
Mads Marquart
cf829a44e7 ci: fix cargo deny 2024-12-21 09:05:36 +03:00
dependabot[bot]
f2fb7dee3d build(deps): bump EmbarkStudios/cargo-deny-action
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-12-21 09:05:36 +03:00
Skip R.
836e717021 macOS: Fix checking for undocumented cursors (#4033)
`AnyClass::responds_to` delegates to `class_respondsToSelector`, a
function provided by the Objective-C runtime. However, at some point,
this began to return `false` for selectors referring to undocumented
cursors, despite the cursors remaining accessible via said selectors.
That this check fails prevents the cursors from being used.

We can instead send `respondsToSelector:` to the `NSCursor` class
itself. As an instance method, this is nominally impossible; however,
Apple grants an exemption[1] that permits class objects to perform
instance methods defined in the root class.

Checking for the undocumented cursors in this way gets them working
again, at least on macOS Sequoia 15.1.1.

[1]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocObjectsClasses.html#//apple_ref/doc/uid/TP30001163-CH11-TPXREF120
2024-12-21 09:05:36 +03:00
Mads Marquart
1e44c354fe Fix MonitorHandle PartialEq and Hash on iOS (#4013) 2024-12-21 09:05:36 +03:00
Mads Marquart
1fb118031c macOS: Fix crash when pressing Caps Lock (#4024)
Events emitted by `flagsChanged:` cannot access
`charactersIgnoringModifiers`. We were previously doing this because we
were trying to re-use the `create_key_event` function, but that is unsuited
for this purpose, so I have separated the `flagsChanged:` logic out from it.
2024-12-21 09:05:36 +03:00
Mads Marquart
d6ab3dcb86 chore: fix clippy lints 2024-12-21 09:05:36 +03:00
Rodrigo Rivas Costa
d828523b39 x11: move up XInput2 event registration
It should be done before mapping the window, or we could
lose the firsst XInput2 events, such as the first FocusIn.

Fixes #2841.
2024-12-21 09:05:36 +03:00
Nico Burns
fccfd359db macOS: set activation policy by default if app is not bundled (#3961) 2024-12-21 09:05:36 +03:00
lucasmerlin
5c1cc652c4 Basic iOS IME support (#3823)
This implements basic iOS IME support (typing, backspace, support for emojis
etc but no autocomplete or copy / paste menu).

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-12-21 09:05:36 +03:00
Piotr Podusowski
1d18a6ca57 android: use show_soft_input to summon the keyboard
Route it via the `Window::set_ime_allowed` like on iOS.
2024-12-21 09:05:36 +03:00
Mads Marquart
bab2d82fd3 docs: add fn main to root examples
This is not strictly required, but makes the examples a bit easier to
read understand (especially since the `EventLoop` really _should_ be
created inside `fn main`, and not in some random function potentially
running on a random thread).
2024-12-21 09:05:36 +03:00
Matěj Laitl
bbab3d8e35 x11: fix WindowAttributesExtX11::with_x11_screen()
Based on https://github.com/rust-windowing/winit/pull/3973, which should
be merged first.

There's an API to programmatically specify X11 screen id (override what
is determined from the `DISPLAY` env variable), but it doesn't work.

Seeting up X Server with 2 screens and calling `DISPLAY=:0
X11_SCREEN_ID=1 cargo run --example window` should be equivalent to
calling `DISPLAY=:0.1 cargo run --example window`

The latter works (and places the window on the correct screen), but the
former yields

`failed to create initial window: Os(OsError { line: 620, file:
"src/platform_impl/linux/x11/window.rs", error: X11Error(X11Error {
error_kind: Match, error_code: 8, sequence: 219, bad_value: 1319,
minor_opcode: 0, major_opcode: 1, extension_name: None, request_name:
Some("CreateWindow") }) })`

_Here `1319` is the root window id for screen 0, which doesn't match the
screen 1 that we request._

The problem is that we need to factor in the screen id when determining
the parent (root) window when not explicitly set. This patch does that.

---

Also: Extend the window example with X11_{SCREEN,VISUAL}_ID env variables
2024-12-21 09:05:36 +03:00
Matěj Laitl
b02a70e6bb x11: iterate only visuals from the matching screen 2024-12-21 09:05:36 +03:00
AngelicosPhosphoros
9ceb8d4144 Improve waiting for messages on Windows
Previous version used [`SetTimer`] with `GetMessageW` for waiting.
The downside of UI timers like ones created by `SetTimer`,
is that they may be late by up to 15-16 ms.

To fix this behaviour, I added use of high resolution timers created by
[`CreateWaitableTimerExW`] with the flag
`CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
In my previous experience, waiting on such timers have precision of
roundly 0.5 ms which is the best available on Windows at the moment. I
use [`MsgWaitForMultipleObjectsEx`] to wait simultaneously for both
timer and newly arriving events.

Unfortunately, high resolution timers are available only since Windows
10 1803. However:

1. Win 10 is already getting to the end of support, like all previous
   versions, so it is OK to rely on APIs introduced in it;
2. I use `dwMilliseconds` parameter of `MsgWaitForMultipleObjectsEx` as
   a fallback. It should perform not worse compared to waiting for
   events from `SetTimer`.

I also refactored code to remove event dispatching from function
responsible for waiting for events. This provides more clear separations
of concern and avoids unnecessary duplication of dispatching logic.

After [review] from @rib, I also moved the waiting itself from
`wait_for_messages` method to separate function, so it is clearly seen
that `wait_for_messages` do 3 things: notify app that we about to wait,
wait, notify that we have new events.

I have tested behaviour using a egui app with Vulkan rendering with
`VK_PRESENT_MODE_IMMEDIATE_KHR`, and older version consistently have
twice less FPS than requested (e.g. 30 FPS when limit is 60 and 60 FPS
when limit is 120) while newer version works more correctly (almost
always 60 FPS when limit is 60, and only 5-10 frames missing when FPS is
set to 120 or more).

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

[`CreateWaitableTimerExW`]: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createwaitabletimerexw
[`MsgWaitForMultipleObjectsEx`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjectsex
[`SetTimer`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer
[review]: https://github.com/rust-windowing/winit/pull/3950#discussion_r1800184479
2024-12-21 09:05:36 +03:00
Shane Celis
485ae90aae macOS: fix panic during drag_window
Return error from it instead of unwrapping.
2024-12-21 09:05:36 +03:00
Kirill Chibisov
107bf91a2d x11: don't forward key events to IME when it's disabled
Fixes #3815.
2024-12-21 09:05:36 +03:00
Mads Marquart
1bc405e526 macOS: Fix move event sometimes being triggered on resize (#3914) 2024-12-21 09:05:36 +03:00
Kirill Chibisov
685090021b chore: fix nightly CI on linux/ios
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-12-21 09:05:36 +03:00
purajit
cfe7eb8954 Prevent winit from overriding LSUIElement in package manifests (#3920) 2024-12-21 09:05:36 +03:00
Tarek Abdel Sater
838b2459a7 macOS: add option to explicitly hide menu/dock in Borderless (#3882) 2024-12-21 09:05:36 +03:00
John Nunley
f4a5ad9c22 x11: use more information in X11 "not supported" errors
This makes it so, when X11 fails to initialize due to not loading a
library, it provides more verbose information on what exactly happened.

Fixes #3883.
Signed-off-by: John Nunley <dev@notgull.net>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-12-21 09:05:36 +03:00
John Nunley
00ecd1533e m: Ignore mutex poisoning in X11_BACKEND
A panic doesn't really put any of the fields in XConnection into an invalid
state, so there is no real reason to panic when poisoning is detected.
So just ignore the poison.

Closes #3870

Signed-off-by: John Nunley <dev@notgull.net>
2024-12-21 09:05:36 +03:00
Kirill Chibisov
114512bb32 Winit version 0.30.5 2024-08-08 21:26:00 +03:00
Kirill Chibisov
8c07319ced chore: drop libera link
It has low amount of users and not active, so better direct people
straight to matrix at this point.
2024-08-08 21:26:00 +03:00
daxpedda
fa3ec006e2 Add ActiveEventLoop::system_theme()
This also fixes macOS returning `None` in `Window::theme()` if no theme
override is set, instead it now returns the system theme.

MacOS and Wayland were the only ones working correctly according to the
documentation, which was an oversight. The documentation was "fixed"
now.

Fixes #3837.
2024-08-08 21:26:00 +03:00
Pascal Hertleif
f3d1f922f9 macOS: skip releasing unavailable monitors
Prevent assertion error when trying to close a fullscreen window that
was on a display that got disconnected.
2024-08-08 21:26:00 +03:00
daxpedda
77e8fe6094 Windows: use Box::leak() instead of Box::into_raw()
This was detected by a new change in Nightly Rust that applied
`#[must_use]` to the return value of `Box::into_raw()`.
2024-08-08 21:26:00 +03:00
Kirill Chibisov
5fce4a09de android: add {Active,}EventLoopExtAndroid::android_app
This type comes from the user and stored for the entire lifetime, so
no need to hide it from them after they've passed it to winit.

Fixes #3818.
2024-08-08 21:26:00 +03:00
daxpedda
5735786f42 Fix CI for Rust v1.80 (#3822)
`clippy::doc_lazy_continuation` was added, which needed some fixing from our side.
2024-08-08 21:26:00 +03:00
daxpedda
9ca8c09208 Web: don't block pen input (#3813) 2024-08-08 21:26:00 +03:00
daxpedda
4b1aa51094 macOS: fix building with feature = "rwh_04" 2024-08-08 21:26:00 +03:00
daxpedda
1dec9b4b33 Web: implement Error for CustomCursorError (#3793) 2024-08-08 21:26:00 +03:00
Kirill Chibisov
04d8a284a0 Winit version 0.30.4 2024-07-16 21:04:25 +03:00
daxpedda
949cb0f203 Web: fix MouseMotion coordinate space (#3770) 2024-07-16 21:04:25 +03:00
daxpedda
bf68ac0b14 Web: fix WindowEvent::Resized not using rAF (#3790) 2024-07-16 21:04:25 +03:00
Kirill Chibisov
ba188376d1 wayland: bump dependencies
Update SCTK as a follow-up to 1170554dbd (ignore events to dead
objects) fixing it for wl_output.
2024-07-16 21:04:25 +03:00
daxpedda
6509f8a18b Make DeviceId/WindowId::dummy() safe (#3784) 2024-07-16 21:04:25 +03:00
daxpedda
71dea4637d Fix CI (#3775) 2024-07-16 21:04:25 +03:00
Kirill Chibisov
ec24c3efd3 wayland: ignore events for dead objects
Nothing wrong will happen if we ignore events when compositor is at
wrong, at least crashing because compositor is just _wrong_ probably is
not a great option.

Links: https://github.com/alacritty/alacritty/issues/8065
2024-07-16 21:04:25 +03:00
Mads Marquart
feca480b4c Avoid path when importing modules (#3755)
Rust tooling generally works better this way. This includes
rust-analyzer, but more noticeably the output from `tracing` typically
prints the module path, which did not correspond to the actual file
system before.

Concretely, tracing output from the macOS backend changes from printing:
`winit::platform_impl::platform::util`
To printing:
`winit::platform_impl::macos::util`
2024-07-16 21:04:25 +03:00
John Nunley
655fdc896f ci: Use taiki-e/checkout-action
taiki-e/checkout-action has a few advantages over actions/checkout,
such as:

- It is written in Bash rather than Node.js
- It does not have frequent breaking changes, reducing churn

Signed-off-by: John Nunley <dev@notgull.net>
2024-07-16 21:04:25 +03:00
msiglreith
faa641e57f Add notgull as Windows maintainer 2024-07-16 21:04:25 +03:00
msiglreith
7c81364e1c Update codeowner list 2024-07-16 21:04:25 +03:00
Bruce Mitchener
6abfef1220 Use default-features, not default_features (#3746)
The latter syntax is deprecated and will be removed in Rust
2024 edition. This also generates a warning with current
versions of Rust.
2024-07-16 21:04:25 +03:00
Kirill Chibisov
d2d4d20108 Winit version 0.30.3 2024-06-21 20:16:14 +03:00
daxpedda
d8f4d8f1b7 Web: implement WaitUntilStrategy (#3739) 2024-06-21 20:16:14 +03:00
daxpedda
a974640a66 Web: set control flow strategies on EventLoop (#3740) 2024-06-21 20:16:14 +03:00
Mads Marquart
3d7d766182 macOS: set the theme on the NSWindow, instead of application-wide
This new implementation uses:
- The NSAppearanceCustomization protocol for retrieving the appearance
  of the window, instead of using the application-wide
  `-[NSApplication effectiveAppearance]`.
- Key-Value observing for observing the `effectiveAppearance` to compute
  the `ThemeChanged` event, instead of using the undocumented
  `AppleInterfaceThemeChangedNotification` notification.

This also fixes `WindowBuilder::with_theme` not having any effect, and
the conversion between `Theme` and `NSAppearance` is made a bit more
robust.
2024-06-21 20:16:14 +03:00
Kirill Chibisov
c73d8cff20 x11: fix build on arm
The c_char type, which was used under the hood is different depending
on arch, thus use it directly instead of i8.

Fixes #3735.
2024-06-21 20:16:14 +03:00
Kirill Chibisov
79aa95b212 Winit version 0.30.2 2024-06-15 20:00:04 +03:00
Kirill Chibisov
ecd14688dc Revert: Web: don't wait for polling when sending events
This is a breaking change, thus revert it for patch series.
2024-06-15 20:00:04 +03:00
Kirill Chibisov
b512ed1e63 macOS: fix opacity handling
Not using `NSColor::clearColor()` results in Quartz thinking that the
window is not transparent at all, which results in artifacts.

However, not setting the `windowBackgroundColor` in
`Window::set_transparent` results in border not properly rendered.

Fixes: 94664ff687 (Don't set the background color)
2024-06-15 20:00:04 +03:00
Kirill Chibisov
96388f4f6b chore: address 1.79 clippy lints 2024-06-15 20:00:04 +03:00
daxpedda
1745b01502 Web: fix crash InnerSizeWriter::request_inner_size() (#3727) 2024-06-15 20:00:04 +03:00
daxpedda
54e974c090 Web: don't overwrite cursor with CursorIcon::Default (#3729) 2024-06-15 20:00:04 +03:00
daxpedda
b14d5c0c99 Web: queue EventLoopProxy::send_event() to microtask 2024-06-15 20:00:04 +03:00
John Nunley
437747b966 Winit version 0.30.1
Signed-off-by: John Nunley <dev@notgull.net>
2024-06-10 18:40:33 +03:00
Mads Marquart
21e266f3b7 macOS/iOS: Various refactorings in application state (#3720)
I'm preparing to get rid of our application delegate in favour of registering
notification observers, to do so I'm renaming `app_delegate.rs` to
`app_state.rs`, and moving the functionality out of the Objective-C method
into a normal method.

Additionally, `AppState` previously implemented `Default`, but really, this
was a hack done because someone (probably myself) was too lazy to write out
the full initialization in `AppDelegate::new`.
2024-06-10 18:40:33 +03:00
Mads Marquart
3a0928af45 macOS: Improve event queuing (#3708)
* Use AppKit's internal queuing mechanisms

This allows events to be queued in a more consistent order, they're now
interleaved with events that we handle immediately (like redraw events),
instead of being handled afterwards.

* Only queue events if necessary

This makes the call stack / backtraces easier to understand whenever
possible, and generally improves upon the order in which events are
delivered.
2024-06-10 18:40:33 +03:00
Philippe Renon
ad92b4f89d doc: clarify Window::pre_present_notify availability
Fixes #3703.
2024-06-10 18:40:33 +03:00
ShikiSuen
bf4445bb62 Handle _selected_range sent to NSTextInputClient.setMarkedText(). (#3619)
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-06-10 18:40:33 +03:00
Mads Marquart
391a22217d Implement ApplicationHandler for &mut A and Box<A> (#3709) 2024-06-10 18:40:33 +03:00
Mads Marquart
fb4a674ee5 Update objc2 to v0.2.2 (#3702)
- Use new `bitflags!` support.
- Use `objc2-ui-kit`.
- Change usage of `Id` to `Retained`.
2024-06-10 18:40:33 +03:00
Diggory Hardy
43f296b2b3 event_loop: add is_x11 and is_wayland on EventLoop 2024-06-10 18:40:33 +03:00
Golden Water
042667c5eb Resize when size changes on scale change on macOS
This fixes an issue where the window glitched due to resize
when the user doesn't actually change the window, but macOS
function to update window size was still called.
2024-06-10 18:40:33 +03:00
Kirill Chibisov
3206d105fe chore: explicitly use cfg_aliases 0.2.1
This correctly handles recent nightly lint that requires to explicitly
define the CFG guards.
2024-06-10 18:40:33 +03:00
Kevin Müller
1afec3ca0d bugfix: Replace pointer dereference with read_unaligned
On Raspberry Pi, using the Rust crate eframe caused the program to crash on
mouse movement. The Backtrace lead to this specific line of code, and the exact
error was a "misaligned pointer dereference: address must be a multiple of 0x8
but is xxxx"

The edit has been tested with the Raspberry Pi, which works now.
2024-06-10 18:40:33 +03:00
Ryan Burleson
c4a8e9321d fix doc typo in application.rs (#3676) 2024-06-10 18:40:33 +03:00
linkmauve
dee7a405fc Reexport older versions of raw-window-handle
When the user decides to use an older version of raw-window-handle,
through the rwh_04 or rwh_05 features, it makes sense to reexport the
crate so they don’t have to depend on it manually and can instead use
winit::raw_window_handle.
2024-06-10 18:40:33 +03:00
Mads Marquart
a298b4d00e Reduce usage of direct msg_send! 2024-06-10 18:40:33 +03:00
Mads Marquart
aebd5edc9e macOS: Move util::EMPTY_RANGE to usage spot (#3685) 2024-06-10 18:40:33 +03:00
Mads Marquart
c801b69d3e Retain ApplicationDelegate in NSWindowDelegate and NSView
The delegate is only weakly referenced by NSApplication, so getting it
from there may fail if the event loop has been dropped.

Fixes #3668.
2024-06-10 18:40:33 +03:00
Mads Marquart
ebd6454f8f Use rustc-check-cfg (#3682) 2024-06-10 18:40:33 +03:00
daxpedda
4f2f0bc08f Web: fix Clippy v1.78 FPs (#3678) 2024-06-10 18:40:33 +03:00
Kirill Chibisov
4b3c0655bf Winit version 0.30.0 2024-04-27 19:00:38 +04:00
Joshua Pedrick
0812adc983 Add UIGestureRecognizerDelegate and PanGestureRecogniser (#3597)
- Allow all gestures simultaneously recognized.
- Add PanGestureRecogniser with min/max number of touches.
- Fix sending delta values relative to Update instead to match macOS.
- Fix rotation gesture units from iOS to be in degrees instead of radians.

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-04-27 19:00:38 +04:00
Mads Marquart
cd6ec19300 Don't set the background color when initializing with transparency (#3657)
Setting the background color changes how the window title bar appears,
which is something that the application should customize itself if it
wants this behaviour (and also, it wasn't set when calling
`set_transparent`, so the behaviour wasn't consistent).
2024-04-27 19:00:38 +04:00
growfrow
61bd8172bd chore: fix some typos in comments (#3635)
Signed-off-by: growfrow <growfrow@outlook.com>
2024-04-27 19:00:38 +04:00
Kirill Chibisov
c04c113e7e chore: ensure that .cargo config is not published
Just in case, so the correct changelog will be rendered when pulling
the crate from the crates.io as archive and trying to build it.
2024-04-27 19:00:38 +04:00
Marijn Suijten
ce32a3024e android: bump to ndk 0.9.0 and android-activity 0.6.0 2024-04-27 19:00:38 +04:00
120 changed files with 4133 additions and 2454 deletions

9
.github/CODEOWNERS vendored
View File

@@ -2,10 +2,9 @@
/src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95
# Apple (AppKit + UIKit)
# iOS
/src/platform/ios.rs @madsmtm
/src/platform/macos.rs @madsmtm
/src/platform_impl/apple @madsmtm
/src/platform_impl/ios @madsmtm
# Unix
/src/platform_impl/linux/mod.rs @kchibisov
@@ -18,6 +17,10 @@
/src/platform/x11.rs @kchibisov @notgull
/src/platform_impl/linux/x11 @kchibisov @notgull
# macOS
/src/platform/macos.rs @madsmtm
/src/platform_impl/macos @madsmtm
# Web
/src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda

View File

@@ -42,7 +42,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.73']
toolchain: [stable, nightly, '1.70.0']
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -61,10 +61,10 @@ jobs:
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Android is tested on stable-3
- toolchain: '1.73'
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
- toolchain: '1.73'
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: {
@@ -107,7 +107,12 @@ jobs:
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
run: |
cargo generate-lockfile
cargo update -p ahash --precise 0.8.7
cargo update -p bumpalo --precise 3.14.0
cargo update -p objc2-encode --precise 4.0.3
cargo update -p orbclient --precise 0.3.47
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -149,13 +154,13 @@ jobs:
- name: Test dpi crate
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo test -p dpi
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS
- name: Run tests
@@ -164,7 +169,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS
- name: Lint with clippy
@@ -174,7 +179,7 @@ jobs:
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled
@@ -183,7 +188,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation
@@ -221,7 +226,7 @@ jobs:
steps:
- uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
log-level: error

View File

@@ -19,6 +19,11 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your
life during review it's recommended to check the "give contributors write access
to the branch" checkbox.
We use unstable Rustfmt options across the project, so please run
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
the maintainers can do it for you before merging, just state so in your pull
request description.
#### Handling review
During the review process certain events could require an action from your side,

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.30.3"
version = "0.30.11"
authors = [
"The winit contributors",
"Pierre Krieger <pierre.krieger1708@gmail.com>",
@@ -14,7 +14,17 @@ rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
exclude = ["/.cargo"]
include = [
"/build.rs",
"/docs",
"/examples",
"/FEATURES.md",
"/LICENSE",
"/src",
"!/src/platform_impl/web/script",
"/src/platform_impl/web/script/**/*.min.js",
"/tests",
]
[package.metadata.docs.rs]
features = [
@@ -102,21 +112,21 @@ softbuffer = { version = "0.4.0", default-features = false, features = [
"wayland-dlopen",
] }
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
ndk = { version = "0.9.0", default-features = false }
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
block2 = "0.5.1"
core-foundation = "0.9.3"
objc2 = "0.5.2"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
block2 = "0.5.1"
objc2-foundation = { version = "0.2.2", features = [
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"NSArray",
@@ -134,8 +144,11 @@ objc2-foundation = { version = "0.2.2", features = [
"NSString",
"NSThread",
"NSValue",
] }
objc2-app-kit = { version = "0.2.2", features = [
]
[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit]
version = "0.2.2"
features = [
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
@@ -162,27 +175,34 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
]
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-foundation = { version = "0.2.2", features = [
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSProcessInfo",
"NSThread",
"NSSet",
] }
objc2-ui-kit = { version = "0.2.2", features = [
]
[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit]
version = "0.2.2"
features = [
"UIApplication",
"UIDevice",
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
@@ -196,12 +216,14 @@ objc2-ui-kit = { version = "0.2.2", features = [
"UIView",
"UIViewController",
"UIWindow",
] }
]
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
windows-sys = { version = "0.52.0", features = [
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.52.0"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
@@ -212,6 +234,7 @@ windows-sys = { version = "0.52.0", features = [
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
@@ -226,13 +249,12 @@ windows-sys = { version = "0.52.0", features = [
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
] }
]
# Linux
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
calloop = "0.13.0"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
@@ -242,18 +264,18 @@ rustix = { version = "0.38.4", default-features = false, features = [
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.9.0", default-features = false, optional = true }
wayland-backend = { version = "0.3.0", default-features = false, features = [
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [
wayland-client = { version = "0.31.10", optional = true }
wayland-protocols = { version = "0.32.8", features = [
"staging",
], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [
wayland-protocols-plasma = { version = "0.3.8", features = [
"client",
], optional = true }
x11-dl = { version = "2.19.1", optional = true }
@@ -267,61 +289,67 @@ x11rb = { version = "0.13.0", default-features = false, features = [
], optional = true }
xkbcommon-dl = "0.4.2"
# Orbital
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
# Web
[target.'cfg(target_family = "wasm")'.dependencies]
web_sys = { package = "web-sys", version = "0.3.64", features = [
'AbortController',
'AbortSignal',
'Blob',
'BlobPropertyBag',
'console',
'CssStyleDeclaration',
'Document',
'DomException',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Worker',
'Url',
] }
js-sys = "0.3.64"
js-sys = "0.3.70"
pin-project = "1"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70", features = [
"AbortController",
"AbortSignal",
"Blob",
"BlobPropertyBag",
"console",
"CssStyleDeclaration",
"Document",
"DomException",
"DomRect",
"DomRectReadOnly",
"Element",
"Event",
"EventTarget",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
"ImageBitmapRenderingContext",
"ImageData",
"IntersectionObserver",
"IntersectionObserverEntry",
"KeyboardEvent",
"MediaQueryList",
"MessageChannel",
"MessagePort",
"Navigator",
"Node",
"OrientationLockType",
"OrientationType",
"PageTransitionEvent",
"Permissions",
"PermissionState",
"PermissionStatus",
"PointerEvent",
"PremultiplyAlpha",
"ResizeObserver",
"ResizeObserverBoxOptions",
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
"Screen",
"ScreenOrientation",
"Url",
"VisibilityState",
"WheelEvent",
"Window",
"Worker",
] }
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"
@@ -340,7 +368,7 @@ resolver = "2"
members = ["dpi"]
[workspace.package]
rust-version = "1.73"
rust-version = "1.70.0"
repository = "https://github.com/rust-windowing/winit"
license = "Apache-2.0"
edition = "2021"

View File

@@ -154,6 +154,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the device idiom
* Getting the preferred video mode
### Web

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.3"
winit = "0.30.11"
```
## [Documentation](https://docs.rs/winit)
@@ -19,7 +19,7 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit).
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room.
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
@@ -33,9 +33,13 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
show something on the window you need to use the platform-specific getters provided by winit, or
another library.
## CONTRIBUTING
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following

View File

@@ -10,9 +10,10 @@ fn main() {
android_platform: { target_os = "android" },
web_platform: { all(target_family = "wasm", target_os = "unknown") },
macos_platform: { target_os = "macos" },
ios_platform: { all(target_vendor = "apple", not(target_os = "macos")) },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
free_unix: { all(unix, not(target_vendor = "apple"), not(android_platform), not(target_os = "emscripten")) },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" },
// Native displays.

View File

@@ -12,6 +12,4 @@ disallowed-methods = [
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ path = "core_foundation::runloop::kCFRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" },
{ path = "objc2_foundation::NSRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" }
]

View File

@@ -1,15 +1,20 @@
# https://embarkstudios.github.io/cargo-deny/
# https://embarkstudios.github.io/cargo-deny
# cargo install cargo-deny
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
# cargo update && cargo deny --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
[graph]
all-features = true
exclude-dev = true
targets = [
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown" },
{ triple = "wasm32-unknown-unknown", features = [
"atomics",
] },
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
@@ -18,46 +23,56 @@ targets = [
{ triple = "x86_64-unknown-redox" },
]
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
ignore = []
[licenses]
allow = [
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"ISC", # https://tldrlegal.com/license/isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
]
confidence-threshold = 1.0
private = { ignore = true }
[bans]
multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update
{ crate = "raw-window-handle", reason = "we depend on multiple behind features" },
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
]
skip-tree = []
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
[bans.build]
include-archives = true
interpreted = "deny"
[licenses]
private = { ignore = true }
unlicensed = "deny"
allow-osi-fsf-free = "neither"
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
copyleft = "deny"
[[bans.build.bypass]]
allow = [
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["ci/*", "githooks/*"]
crate = "zerocopy"
[[bans.build.bypass]]
allow-globs = ["freetype2/*"]
crate = "freetype-sys"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_i686_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_i686_msvc"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_x86_64_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_x86_64_msvc"

View File

@@ -3,75 +3,15 @@
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowId};
use winit::window::Window;
#[path = "util/fill.rs"]
mod fill;
#[derive(Default)]
struct Application {
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, Window>,
}
impl ApplicationHandler for Application {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), window);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
}
}
}
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes()
@@ -85,9 +25,58 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.create_window(window_attributes).unwrap()
}
let event_loop = EventLoop::new().unwrap();
let mut app = Application::default();
event_loop.run_app(&mut app)
let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut parent_window_id = None;
event_loop.run(move |event: Event<()>, event_loop| {
match event {
Event::Resumed => {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
parent_window_id = Some(window.id());
println!("Parent window id: {parent_window_id:?})");
windows.insert(window.id(), window);
},
Event::WindowEvent { window_id, event } => match event {
WindowEvent::CloseRequested => {
windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
},
_ => (),
}
})
}
#[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))]

View File

@@ -31,6 +31,8 @@ use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMac
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
#[path = "util/tracing.rs"]
mod tracing;
@@ -44,7 +46,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let event_loop = EventLoop::new()?;
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
let _event_loop_proxy = event_loop.create_proxy();
// Wire the user event from another thread.
@@ -54,7 +56,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// from a different thread.
info!("Starting to send user event every second");
loop {
_event_loop_proxy.wake_up();
let _ = _event_loop_proxy.send_event(UserEvent::WakeUp);
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
@@ -64,6 +66,12 @@ fn main() -> Result<(), Box<dyn Error>> {
event_loop.run_app(&mut state).map_err(Into::into)
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
enum UserEvent {
WakeUp,
}
/// Application state and event handling.
struct Application {
/// Custom cursors assets.
@@ -79,7 +87,7 @@ struct Application {
}
impl Application {
fn new(event_loop: &EventLoop) -> Self {
fn new<T>(event_loop: &EventLoop<T>) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(any(android_platform, ios_platform)))]
let context = Some(
@@ -134,6 +142,28 @@ impl Application {
window_attributes = window_attributes.with_activation_token(token);
}
#[cfg(x11_platform)]
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
#[cfg(x11_platform)]
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => info!(
"Set the X11_SCREEN_ID env variable to place the window on non-default screen"
),
}
#[cfg(macos_platform)]
if let Some(tab_id) = _tab_id {
window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
@@ -302,9 +332,9 @@ impl Application {
}
}
impl ApplicationHandler for Application {
fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
info!("User wake up");
impl ApplicationHandler<UserEvent> for Application {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
info!("User event: {event:?}");
}
fn window_event(

View File

@@ -5,7 +5,7 @@ use crate::event_loop::ActiveEventLoop;
use crate::window::WindowId;
/// The handler of the application events.
pub trait ApplicationHandler {
pub trait ApplicationHandler<T: 'static = ()> {
/// Emitted when new events arrive from the OS to be processed.
///
/// This is a useful place to put code that should be done before you start processing
@@ -82,95 +82,11 @@ pub trait ApplicationHandler {
/// [`Suspended`]: Self::suspended
fn resumed(&mut self, event_loop: &ActiveEventLoop);
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
/// Emitted when an event is sent from [`EventLoopProxy::send_event`].
///
/// Multiple calls to the aforementioned method will be merged, and will only wake the event
/// loop once; however, due to the nature of multi-threading some wake ups may appear
/// spuriously. For these reasons, you should not rely on the number of times that this was
/// called.
///
/// The order in which this is emitted in relation to other events is not guaranteed. The time
/// at which this will be emitted is not guaranteed, only that it will happen "soon". That is,
/// there may be several executions of the event loop, including multiple redraws to windows,
/// between [`EventLoopProxy::wake_up()`] being called and the event being delivered.
///
/// [`EventLoopProxy::wake_up()`]: crate::event_loop::EventLoopProxy::wake_up
///
/// # Example
///
/// Use a [`std::sync::mpsc`] channel to handle events from a different thread.
///
/// ```no_run
/// use std::sync::mpsc;
/// use std::thread;
/// use std::time::Duration;
///
/// use winit::application::ApplicationHandler;
/// use winit::event_loop::{ActiveEventLoop, EventLoop};
///
/// struct MyApp {
/// receiver: mpsc::Receiver<u64>,
/// }
///
/// impl ApplicationHandler for MyApp {
/// # fn window_event(
/// # &mut self,
/// # _event_loop: &ActiveEventLoop,
/// # _window_id: winit::window::WindowId,
/// # _event: winit::event::WindowEvent,
/// # ) {
/// # }
/// #
/// # fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
/// // Iterate current events, since wake-ups may have been merged.
/// //
/// // Note: We take care not to use `recv` or `iter` here, as those are blocking,
/// // and that would be bad for performance and might lead to a deadlock.
/// for i in self.receiver.try_iter() {
/// println!("received: {i}");
/// }
/// }
///
/// // Rest of `ApplicationHandler`
/// }
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = EventLoop::new()?;
///
/// let (sender, receiver) = mpsc::channel();
///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop
/// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || {
/// let mut i = 0;
/// loop {
/// println!("sending: {i}");
/// if sender.send(i).is_err() {
/// // Stop sending once `MyApp` is dropped
/// break;
/// }
/// // Trigger the wake-up _after_ we placed the event in the channel.
/// // Otherwise, `proxy_wake_up` might be triggered prematurely.
/// proxy.wake_up();
/// i += 1;
/// thread::sleep(Duration::from_secs(1));
/// }
/// });
///
/// event_loop.run_app(&mut app)?;
///
/// drop(app);
/// background_thread.join().unwrap();
///
/// Ok(())
/// }
/// ```
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop;
/// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
let _ = (event_loop, event);
}
/// Emitted when the OS sends an event to a winit window.
@@ -308,7 +224,7 @@ pub trait ApplicationHandler {
}
}
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &mut A {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -320,8 +236,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
}
#[inline]
@@ -365,7 +281,7 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
}
}
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for Box<A> {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -377,8 +293,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
}
#[inline]

View File

@@ -39,27 +39,3 @@ The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased
### Changed
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
- Bump MSRV from `1.70` to `1.73`.
- Changed `ApplicationHandler::user_event` to `user_wake_up`, removing the
generic user event.
Winit will now only indicate that wake up happened, you will have to pair
this with an external mechanism like `std::sync::mpsc::channel` if you want
to send specific data to be processed on the main thread.
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
only wakes up the loop.
### Removed
- Remove `EventLoop::run`.
- Remove `EventLoopExtRunOnDemand::run_on_demand`.
- Remove `EventLoopExtPumpEvents::pump_events`.
- Remove `Event`.
- On iOS, remove `platform::ios::EventLoopExtIOS` and related `platform::ios::Idiom` type.
This feature was incomplete, and the equivalent functionality can be trivially achieved outside
of `winit` using `objc2-ui-kit` and calling `UIDevice::currentDevice().userInterfaceIdiom()`.

View File

@@ -137,7 +137,7 @@
- On X11, non-resizable windows now have maximize explicitly disabled.
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`
and `WindowEvent::HoveredFile`.
and `WindowEvent::HoveredFile`.
- On Mac, implement `DeviceEvent::Button`.
- Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`.
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.

View File

@@ -1,3 +1,125 @@
## 0.30.11
### Fixed
- On Windows, fixed crash in should_apps_use_dark_mode() for Windows versions < 17763.
- On Wayland, fixed `pump_events` driven loop deadlocking when loop was not drained before exit.
## 0.30.10
### Added
- On Windows, add `IconExtWindows::from_resource_name`.
- On Windows, add `CursorGrabMode::Locked`.
- On Wayland, add `WindowExtWayland::xdg_toplevel`.
### Changed
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
- On iOS, remove custom application delegates. You are now allowed to override the
application delegate yourself.
- On iOS, no longer act as-if the application successfully open all URLs. Override
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
### Fixed
- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw.
- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game`
- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode
- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus.
- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`.
- On Wayland, apply fractional scaling to custom cursors.
- On macOS, fixed `run_app_on_demand` returning without closing open windows.
- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates.
- On macOS, store monitor handle to avoid panics after going in/out of sleep.
- On macOS, allow certain invalid monitor handles and return `None` instead of panicking.
- On Windows, fixed `Ime::Preedit` cursor offset calculation.
## 0.30.9
### Changed
- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`.
### Fixed
- On X11, fix crash with uim.
- On X11, fix modifiers for keys that were sent by the same X11 request.
- On iOS, fix high CPU usage even when using `ControlFlow::Wait`.
## 0.30.8
### Added
- `ActivationToken::from_raw` and `ActivationToken::into_raw`.
- On X11, add a workaround for disabling IME on GNOME.
### Fixed
- On Windows, fixed the event loop not waking on accessibility requests.
- On X11, fixed cursor grab mode state tracking on error.
## 0.30.7
### Fixed
- On X11, fixed KeyboardInput delivered twice when IME enabled.
## 0.30.6
### Added
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
### Fixed
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
default activation policy, unless explicitly provided during initialization.
- On macOS, fix crash when calling `drag_window()` without a left click present.
- On X11, key events forward to IME anyway, even when it's disabled.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
## 0.30.5
### Added
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
### Fixed
- On MacOS, fix building with `feature = "rwh_04"`.
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
- On macOS, fix panic when releasing not available monitor.
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
## 0.30.4
### Changed
- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`.
### Fixed
- On Wayland, avoid crashing when compositor is misbehaving.
- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending
`WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested`
to not be de-duplicated.
- Account for different browser engine implementations of pointer movement coordinate space.
## 0.30.3
### Added
@@ -35,7 +157,6 @@
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
- Add traits `EventLoopExtWayland` and `EventLoopExtX11`, providing methods `is_wayland` and `is_x11` on `EventLoop`.
### Fixed
@@ -112,7 +233,7 @@
`Fn`. The semantics are mostly the same, given that the capture list of the
closure is your new `State`. Consider the following code:
```rust,no_run,ignore
```rust,no_run
use winit::event::Event;
use winit::event_loop::EventLoop;
use winit::window::Window;
@@ -148,7 +269,7 @@
we move particular `match event` arms into methods on `ApplicationHandler`,
for example:
```rust,no_run,ignore
```rust,no_run
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};
@@ -211,7 +332,7 @@
Using the migration example from above, you can change your code as follows:
```rust,no_run,ignore
```rust,no_run
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};

View File

@@ -3,20 +3,20 @@
- Added event `WindowEvent::HiDPIFactorChanged`.
- Added method `MonitorId::get_hidpi_factor`.
- Deprecated `get_inner_size_pixels` and `get_inner_size_points` methods of `Window` in favor of
`get_inner_size`.
`get_inner_size`.
- **Breaking:** `EventsLoop` is `!Send` and `!Sync` because of platform-dependant constraints,
but `Window`, `WindowId`, `DeviceId` and `MonitorId` guaranteed to be `Send`.
- `MonitorId::get_position` now returns `(i32, i32)` instead of `(u32, u32)`.
- Rewrite of the wayland backend to use wayland-client-0.11
- Support for dead keys on wayland for keyboard utf8 input
- Monitor enumeration on Windows is now implemented using `EnumDisplayMonitors` instead of
`EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`.
`EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`.
- On Windows added `MonitorIdExt::hmonitor` method
- Impl `Clone` for `EventsLoopProxy`
- `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found
- Support for touch event on wayland
- `WindowEvent`s `MouseMoved`, `MouseEntered`, and `MouseLeft` have been renamed to
`CursorMoved`, `CursorEntered`, and `CursorLeft`.
`CursorMoved`, `CursorEntered`, and `CursorLeft`.
- New `DeviceEvent`s added, `MouseMotion` and `MouseWheel`.
- Send `CursorMoved` event after `CursorEntered` and `Focused` events.
- Add support for `ModifiersState`, `MouseMove`, `MouseInput`, `MouseMotion` for emscripten backend.

View File

@@ -1,4 +1,4 @@
//! The event enums and assorted supporting types.
//! The [`Event`] enum and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level
@@ -54,15 +54,11 @@ use crate::platform_impl;
use crate::window::Window;
use crate::window::{ActivationToken, Theme, WindowId};
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
// wrap `Event` in some other structure.
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Event {
pub enum Event<T: 'static> {
/// See [`ApplicationHandler::new_events`] for details.
///
/// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
@@ -71,15 +67,18 @@ pub(crate) enum Event {
/// See [`ApplicationHandler::window_event`] for details.
///
/// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event`] for details.
///
/// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: DeviceId, event: DeviceEvent },
/// See [`ApplicationHandler::user_event`] for details.
///
/// [`ApplicationHandler::user_event`]: crate::application::ApplicationHandler::user_event
UserEvent(T),
/// See [`ApplicationHandler::suspended`] for details.
///
/// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended
@@ -104,9 +103,24 @@ pub(crate) enum Event {
///
/// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
MemoryWarning,
}
/// User requested a wake up.
UserWakeUp,
impl<T> Event<T> {
#[allow(clippy::result_large_err)]
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
AboutToWait => Ok(AboutToWait),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
MemoryWarning => Ok(MemoryWarning),
}
}
}
/// Describes the reason the event loop is resuming.
@@ -435,16 +449,13 @@ pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
/// # Notes
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
pub const fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
@@ -521,11 +532,11 @@ pub struct KeyEvent {
/// ## Caveats
///
/// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that
/// implements DVORAK in hardware (or firmware)
/// implements DVORAK in hardware (or firmware)
/// - Your application will likely have to handle keyboards which are missing keys that your
/// own keyboard has.
/// own keyboard has.
/// - Certain `KeyCode`s will move between a couple of different positions depending on what
/// layout the keyboard was manufactured to support.
/// layout the keyboard was manufactured to support.
///
/// **Because of these caveats, it is important that you provide users with a way to configure
/// most (if not all) keybinds in your application.**
@@ -547,8 +558,7 @@ pub struct KeyEvent {
///
/// This has two use cases:
/// - Allows querying whether the current input is a Dead key.
/// - Allows handling key-bindings on platforms which don't
/// support [`key_without_modifiers`].
/// - Allows handling key-bindings on platforms which don't support [`key_without_modifiers`].
///
/// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard
/// shortcuts, **it is important that you provide users with a way to configure your
@@ -556,8 +566,8 @@ pub struct KeyEvent {
/// incompatible keyboard layout.**
///
/// ## Platform-specific
/// - **Web:** Dead keys might be reported as the real key instead
/// of `Dead` depending on the browser/OS.
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
/// browser/OS.
///
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
pub logical_key: keyboard::Key,
@@ -839,8 +849,8 @@ pub struct Touch {
///
/// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**.
/// - **Android**: This will never be [None]. If the device doesn't support pressure
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
@@ -1007,7 +1017,7 @@ mod tests {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = unsafe { event::DeviceId::dummy() };
let did = event::DeviceId::dummy();
#[allow(deprecated)]
{
@@ -1017,7 +1027,8 @@ mod tests {
use crate::window::WindowId;
// Mainline events.
let wid = unsafe { WindowId::dummy() };
let wid = WindowId::dummy();
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
@@ -1101,12 +1112,25 @@ mod tests {
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event: event::Event| {
foreach_event!(|event: event::Event<()>| {
let event2 = event.clone();
assert_eq!(event, event2);
})
}
#[test]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>();
if is_user {
assert_eq!(event2, Err(event::Event::UserEvent(())));
} else {
assert!(event2.is_ok());
}
})
}
#[test]
fn test_force_normalize() {
let force = event::Force::Normalized(0.0);
@@ -1127,12 +1151,12 @@ mod tests {
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event| {
let _ = format!("{:?}", event);
foreach_event!(|event: event::Event<()>| {
let _ = format!("{event:?}");
});
let _ = event::StartCause::Init.clone();
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
let did = crate::event::DeviceId::dummy().clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
set.sort_unstable();

View File

@@ -3,16 +3,15 @@
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up
//! you can poll your event sources.
//! [`send_event`][EventLoopProxy::send_event] method.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::fmt;
use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::{error, fmt};
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
@@ -21,9 +20,10 @@ use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError};
use crate::event::Event;
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
@@ -40,8 +40,8 @@ use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
pub struct EventLoop {
pub(crate) event_loop: platform_impl::EventLoop,
pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
@@ -59,15 +59,16 @@ pub struct ActiveEventLoop {
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::builder`].
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
#[derive(Default)]
pub struct EventLoopBuilder {
pub struct EventLoopBuilder<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder {
impl EventLoopBuilder<()> {
/// Start building a new event loop.
#[inline]
#[deprecated = "use `EventLoop::builder` instead"]
@@ -76,7 +77,7 @@ impl EventLoopBuilder {
}
}
impl EventLoopBuilder {
impl<T> EventLoopBuilder<T> {
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
@@ -111,7 +112,7 @@ impl EventLoopBuilder {
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
@@ -132,7 +133,7 @@ impl EventLoopBuilder {
}
}
impl fmt::Debug for EventLoop {
impl<T> fmt::Debug for EventLoop<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoop { .. }")
}
@@ -146,12 +147,11 @@ impl fmt::Debug for ActiveEventLoop {
/// Set through [`ActiveEventLoop::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -189,12 +189,12 @@ impl ControlFlow {
}
}
impl EventLoop {
impl EventLoop<()> {
/// Create the event loop.
///
/// This is an alias of `EventLoop::builder().build()`.
#[inline]
pub fn new() -> Result<EventLoop, EventLoopError> {
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
Self::builder().build()
}
@@ -204,12 +204,33 @@ impl EventLoop {
///
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
#[inline]
pub fn builder() -> EventLoopBuilder {
EventLoopBuilder { platform_specific: Default::default() }
pub fn builder() -> EventLoopBuilder<()> {
Self::with_user_event()
}
}
impl EventLoop {
impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
pub fn with_user_event() -> EventLoopBuilder<T> {
EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData }
}
/// See [`run_app`].
///
/// [`run_app`]: Self::run_app
#[inline]
#[deprecated = "use `EventLoop::run_app` instead"]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &ActiveEventLoop),
{
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
self.event_loop.run(event_handler)
}
/// Run the application with the event loop on the calling thread.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
@@ -240,13 +261,13 @@ impl EventLoop {
/// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web.
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop.run_app(app)
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy() }
}
@@ -302,14 +323,14 @@ impl EventLoop {
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for EventLoop {
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target())
}
}
#[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for EventLoop {
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target())
@@ -317,7 +338,7 @@ unsafe impl rwh_05::HasRawDisplayHandle for EventLoop {
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
@@ -331,7 +352,7 @@ impl AsFd for EventLoop {
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
@@ -416,6 +437,17 @@ impl ActiveEventLoop {
self.p.listen_device_events(allowed);
}
/// Returns the current system theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
pub fn system_theme(&self) -> Option<Theme> {
self.p.system_theme()
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
@@ -428,7 +460,7 @@ impl ActiveEventLoop {
/// This exits the event loop.
///
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
/// See [`LoopExiting`][Event::LoopExiting].
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -469,7 +501,7 @@ unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
@@ -513,39 +545,53 @@ unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
}
}
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
#[derive(Clone)]
pub struct EventLoopProxy {
event_loop_proxy: platform_impl::EventLoopProxy,
/// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
}
impl EventLoopProxy {
/// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
/// called.
///
/// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
/// documentation on that for details.
///
/// If the event loop is no longer running, this is a no-op.
///
/// [`proxy_wake_up`]: ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.event_loop_proxy.wake_up();
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { event_loop_proxy: self.event_loop_proxy.clone() }
}
}
impl fmt::Debug for EventLoopProxy {
impl<T: 'static> EventLoopProxy<T> {
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
/// function.
///
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
///
/// [`UserEvent(event)`]: Event::UserEvent
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
let _span = tracing::debug_span!("winit::EventLoopProxy::send_event",).entered();
self.event_loop_proxy.send_event(event)
}
}
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopProxy { .. }")
}
}
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
/// no longer exists.
///
/// Contains the original event given to [`EventLoopProxy::send_event`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl<T> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tried to wake up a closed `EventLoop`")
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum DeviceEvents {
@@ -583,3 +629,23 @@ impl AsyncRequestSerial {
Self { serial }
}
}
/// Shim for various run APIs.
#[inline(always)]
pub(crate) fn dispatch_event_for_app<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
event_loop: &ActiveEventLoop,
event: Event<T>,
) {
match event {
Event::NewEvents(cause) => app.new_events(event_loop, cause),
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
Event::UserEvent(event) => app.user_event(event_loop, event),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
}
}

View File

@@ -1232,7 +1232,7 @@ pub enum NamedKey {
Dimmer,
/// Swap video sources. (`VK_DISPLAY_SWAP`)
DisplaySwap,
/// Select Digital Video Rrecorder. (`KEYCODE_DVR`)
/// Select Digital Video Recorder. (`KEYCODE_DVR`)
DVR,
/// Exit the current application. (`VK_EXIT`)
Exit,

View File

@@ -7,7 +7,12 @@
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//! let event_loop = EventLoop::new().unwrap();
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//! // ...
//! }
//! ```
//!
//! Then you create a [`Window`] with [`create_window`].
@@ -19,11 +24,12 @@
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`].
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if
//! desired.
//!
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works
@@ -83,19 +89,22 @@
//! }
//! }
//!
//! let event_loop = EventLoop::new().unwrap();
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! event_loop.set_control_flow(ControlFlow::Poll);
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! event_loop.set_control_flow(ControlFlow::Poll);
//!
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//!
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! }
//! ```
//!
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
@@ -156,13 +165,12 @@
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes
//! [window_new]: window::Window::new
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`Event::LoopExiting`]: event::Event::LoopExiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
@@ -176,6 +184,9 @@
// doc
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![allow(clippy::missing_safety_doc)]
#![warn(clippy::uninlined_format_args)]
// TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it.
#![cfg_attr(web_platform, allow(unknown_lints, wasm_c_abi))]
#[cfg(feature = "rwh_04")]
pub use rwh_04 as raw_window_handle_04;

View File

@@ -62,7 +62,7 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.3",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.11",
//! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
@@ -76,12 +76,22 @@ use crate::window::{Window, WindowAttributes};
use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
pub trait EventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
impl EventLoopExtAndroid for EventLoop {}
impl<T> EventLoopExtAndroid for EventLoop<T> {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {}
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
@@ -100,7 +110,11 @@ impl WindowExtAndroid for Window {
}
}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {
fn android_app(&self) -> &AndroidApp {
&self.p.app
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
@@ -108,9 +122,9 @@ pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
/// This must be called on Android since the [`AndroidApp`] is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
@@ -119,7 +133,7 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl EventLoopBuilderExtAndroid for EventLoopBuilder {
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
@@ -147,7 +161,7 @@ impl EventLoopBuilderExtAndroid for EventLoopBuilder {
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// For compatibility applications should then import the [`AndroidApp`] type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os = "android")]

View File

@@ -3,11 +3,14 @@
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
//! iOS 9.3.
//!
//! ## Window initialization
//!
//! iOS's main `UIApplicationMain` does some init work that's required by all
//! UI-related code (see issue [#1705]). It is best to create your windows
//! inside `Event::Resumed`.
//! inside [`ApplicationHandler::resumed`].
//!
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
//!
//! ## Building app
//!
@@ -63,12 +66,35 @@
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
//!
//! ## Custom `UIApplicationDelegate`
//!
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
//! though, you might want to access some of the more niche stuff that [the application
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
//! register an application delegate, so you can set up a custom one in a nib file instead.
//!
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
use std::os::raw::c_void;
use crate::event_loop::EventLoop;
use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS {
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom;
}
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
fn idiom(&self) -> Idiom {
self.event_loop.idiom()
}
}
/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
@@ -366,6 +392,24 @@ pub enum ValidOrientations {
Portrait,
}
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Idiom {
Unspecified,
/// iPhone and iPod touch.
Phone,
/// iPad.
Pad,
/// tvOS and Apple TV.
TV,
CarPlay,
}
bitflags::bitflags! {
/// The [edges] of a screen.
///

View File

@@ -3,16 +3,84 @@
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
//! itself), and is regularly tested on macOS 10.14.
//!
//! ## Window initialization
//!
//! A lot of functionality expects the application to be ready before you
//! start doing anything; this includes creating windows, fetching monitors,
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
//!
//! If you encounter problems, you should try doing your initialization inside
//! `Event::Resumed`.
//! [`ApplicationHandler::resumed`].
//!
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
//!
//! ## Custom `NSApplicationDelegate`
//!
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
//! increase the API surface by quite a lot.
//!
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
//!
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
//! to register your own application delegate, as outlined in the following example (see
//! `objc2-app-kit` for more detailed information).
#![cfg_attr(target_os = "macos", doc = "```")]
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject;
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop;
//!
//! declare_class!(
//! struct AppDelegate;
//!
//! unsafe impl ClassType for AppDelegate {
//! type Super = NSObject;
//! type Mutability = mutability::MainThreadOnly;
//! const NAME: &'static str = "MyAppDelegate";
//! }
//!
//! impl DeclaredClass for AppDelegate {}
//!
//! unsafe impl NSObjectProtocol for AppDelegate {}
//!
//! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[method(application:openURLs:)]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example.
//! println!("open urls: {application:?}, {urls:?}");
//! }
//! }
//! );
//!
//! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
//! }
//! }
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let event_loop = EventLoop::new()?;
//!
//! let mtm = MainThreadMarker::new().unwrap();
//! let delegate = AppDelegate::new(mtm);
//! // Important: Call `sharedApplication` after `EventLoop::new`,
//! // doing it before is not yet supported.
//! let app = NSApplication::sharedApplication(mtm);
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
//!
//! // event_loop.run_app(&mut my_app);
//! Ok(())
//! }
//! ```
use std::os::raw::c_void;
@@ -94,6 +162,14 @@ pub trait WindowExtMacOS {
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt;
/// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games.
/// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
/// [`Window::set_fullscreen`] is called.
fn set_borderless_game(&self, borderless_game: bool);
/// Getter for the [`WindowExtMacOS::set_borderless_game`].
fn is_borderless_game(&self) -> bool;
}
impl WindowExtMacOS for Window {
@@ -166,6 +242,16 @@ impl WindowExtMacOS for Window {
fn option_as_alt(&self) -> OptionAsAlt {
self.window.maybe_wait_on_main(|w| w.option_as_alt())
}
#[inline]
fn set_borderless_game(&self, borderless_game: bool) {
self.window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
}
#[inline]
fn is_borderless_game(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.is_borderless_game())
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
@@ -216,6 +302,8 @@ pub trait WindowAttributesExtMacOS {
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
fn with_borderless_game(self, borderless_game: bool) -> Self;
}
impl WindowAttributesExtMacOS for WindowAttributes {
@@ -284,12 +372,21 @@ impl WindowAttributesExtMacOS for WindowAttributes {
self.platform_specific.option_as_alt = option_as_alt;
self
}
#[inline]
fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.platform_specific.borderless_game = borderless_game;
self
}
}
pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application.
/// Sets the activation policy for the application. If used, this will override
/// any relevant settings provided in the package manifest.
/// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent
/// the application from running as an "agent", even if LSUIElement is set to true.
///
/// It is set to [`ActivationPolicy::Regular`] by default.
/// If unused, the Winit will honor the package manifest.
///
/// # Example
///
@@ -338,10 +435,10 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy;
self.platform_specific.activation_policy = Some(activation_policy);
self
}

View File

@@ -1,10 +1,14 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
use crate::event_loop::EventLoop;
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent: 'static;
/// Pump the `EventLoop` to check for and dispatch pending events.
///
/// This API is designed to enable applications to integrate Winit into an
@@ -47,19 +51,19 @@ pub trait EventLoopExtPumpEvents {
/// buffered and handled outside of Winit include:
/// - `RedrawRequested` events, used to schedule rendering.
///
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
///
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
///
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
///
@@ -70,13 +74,13 @@ pub trait EventLoopExtPumpEvents {
///
/// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new new events.
/// Events are delivered via callbacks based on an event loop that is internal to the browser
/// itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so
/// there's no way to support the same approach to polling as on MacOS.
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
///
@@ -99,20 +103,34 @@ pub trait EventLoopExtPumpEvents {
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus;
}
impl EventLoopExtPumpEvents for EventLoop {
fn pump_app_events<A: ApplicationHandler>(
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
self.event_loop.pump_app_events(timeout, app)
#[allow(deprecated)]
self.pump_events(timeout, |event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
/// See [`pump_app_events`].
///
/// [`pump_app_events`]: Self::pump_app_events
#[deprecated = "use EventLoopExtPumpEvents::pump_app_events"]
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
type UserEvent = T;
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.pump_events(timeout, event_handler)
}
}

View File

@@ -1,12 +1,24 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent: 'static;
/// See [`run_app_on_demand`].
///
/// [`run_app_on_demand`]: Self::run_app_on_demand
#[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"]
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
@@ -30,7 +42,9 @@ pub trait EventLoopExtRunOnDemand {
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
/// backend it is possible to use `EventLoopExtWebSys::spawn()`
#[cfg_attr(not(web_platform), doc = "[^1]")]
/// more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -49,23 +63,30 @@ pub trait EventLoopExtRunOnDemand {
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
#[cfg_attr(not(web_platform), doc = "[^1]: `spawn()` is only available on `wasm` platforms.")]
#[rustfmt::skip]
///
#[rustfmt::skip]
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError>;
}
impl EventLoopExtRunOnDemand for EventLoop {
fn run_app_on_demand<A: ApplicationHandler>(
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
#[allow(deprecated)]
self.run_on_demand(|event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.window_target().clear_exit();
self.event_loop.run_app_on_demand(app)
self.event_loop.run_on_demand(event_handler)
}
}

View File

@@ -64,7 +64,7 @@ impl EventLoopExtStartupNotify for ActiveEventLoop {
crate::platform_impl::ActiveEventLoop::X(_) => env::var(X11_VAR),
}
.ok()
.map(ActivationToken::_new)
.map(ActivationToken::from_raw)
}
}
@@ -94,6 +94,6 @@ pub fn reset_activation_token_env() {
///
/// This could be used before running daemon processes.
pub fn set_activation_token_env(token: ActivationToken) {
env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token._token);
env::set_var(X11_VAR, &token.token);
env::set_var(WAYLAND_VAR, token.token);
}

View File

@@ -13,6 +13,10 @@
//! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
use std::ffi::c_void;
use std::ptr::NonNull;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
@@ -38,7 +42,7 @@ pub trait EventLoopExtWayland {
fn is_wayland(&self) -> bool;
}
impl EventLoopExtWayland for EventLoop {
impl<T: 'static> EventLoopExtWayland for EventLoop<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
@@ -57,7 +61,7 @@ pub trait EventLoopBuilderExtWayland {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl EventLoopBuilderExtWayland for EventLoopBuilder {
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
@@ -72,9 +76,25 @@ impl EventLoopBuilderExtWayland for EventLoopBuilder {
}
/// Additional methods on [`Window`] that are specific to Wayland.
pub trait WindowExtWayland {}
///
/// [`Window`]: crate::window::Window
pub trait WindowExtWayland {
/// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window.
fn xdg_toplevel(&self) -> Option<NonNull<c_void>>;
}
impl WindowExtWayland for Window {}
impl WindowExtWayland for Window {
#[inline]
fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
#[allow(clippy::single_match)]
match &self.window {
#[cfg(x11_platform)]
crate::platform_impl::Window::X(_) => None,
#[cfg(wayland_platform)]
crate::platform_impl::Window::Wayland(window) => window.xdg_toplevel(),
}
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland {

View File

@@ -55,7 +55,8 @@ use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::platform_impl::PlatformCustomCursorSource;
@@ -155,6 +156,9 @@ impl WindowAttributesExtWebSys for WindowAttributes {
/// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent: 'static;
/// Initializes the winit event loop.
///
/// Unlike
@@ -177,7 +181,15 @@ pub trait EventLoopExtWebSys {
doc = "[`run_app()`]: EventLoop::run_app()"
)]
/// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
/// See [`spawn_app`].
///
/// [`spawn_app`]: Self::spawn_app
#[deprecated = "use EventLoopExtWebSys::spawn_app"]
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Sets the strategy for [`ControlFlow::Poll`].
///
@@ -208,9 +220,20 @@ pub trait EventLoopExtWebSys {
fn wait_until_strategy(&self) -> WaitUntilStrategy;
}
impl EventLoopExtWebSys for EventLoop {
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
impl<T> EventLoopExtWebSys for EventLoop<T> {
type UserEvent = T;
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, mut app: A) {
self.event_loop.spawn(move |event, event_loop| {
event_loop::dispatch_event_for_app(&mut app, event_loop, event)
});
}
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.spawn(event_handler)
}
fn set_poll_strategy(&self, strategy: PollStrategy) {
@@ -399,7 +422,7 @@ impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animtion"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
}
}
}
@@ -438,3 +461,5 @@ impl Display for CustomCursorError {
}
}
}
impl Error for CustomCursorError {}

View File

@@ -227,7 +227,7 @@ pub trait EventLoopBuilderExtWindows {
F: FnMut(*const c_void) -> bool + 'static;
}
impl EventLoopBuilderExtWindows for EventLoopBuilder {
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
@@ -660,6 +660,17 @@ impl DeviceIdExtWindows for DeviceId {
}
/// Additional methods on `Icon` that are specific to Windows.
///
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
///
/// The `ICON` resource definition statement use the following syntax:
/// ```rc
/// nameID ICON filename
/// ```
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
/// `filename` is the name of the file that contains the resource.
///
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
pub trait IconExtWindows: Sized {
/// Create an icon from a file path.
///
@@ -671,7 +682,12 @@ pub trait IconExtWindows: Sized {
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.
/// Create an icon from a resource embedded in this executable or library by its ordinal id.
///
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
///
/// [`from_resource_name`]: IconExtWindows::from_resource_name
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
@@ -679,6 +695,55 @@ pub trait IconExtWindows: Sized {
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library by its name.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
///
/// # Notes
///
/// Consider the following resource definition statements:
/// ```rc
/// app ICON "app.ico"
/// 1 ICON "a.ico"
/// 0027 ICON "custom.ico"
/// 0 ICON "alt.ico"
/// ```
///
/// Due to some internal implementation details of the resource embedding/loading process on
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
/// [`from_resource`]:
///
/// [`from_resource`]: IconExtWindows::from_resource
///
/// ```rust,no_run
/// use winit::platform::windows::IconExtWindows;
/// use winit::window::Icon;
///
/// assert!(Icon::from_resource_name("app", None).is_ok());
/// assert!(Icon::from_resource(1, None).is_ok());
/// assert!(Icon::from_resource(27, None).is_ok());
/// assert!(Icon::from_resource_name("27", None).is_err());
/// assert!(Icon::from_resource_name("0027", None).is_err());
/// ```
///
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
/// name:
///
/// [`from_resource`]: IconExtWindows::from_resource
///
/// ```rust,no_run
/// # use winit::platform::windows::IconExtWindows;
/// # use winit::window::Icon;
/// assert!(Icon::from_resource_name("0", None).is_ok());
/// assert!(Icon::from_resource(0, None).is_err());
/// ```
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
@@ -694,4 +759,9 @@ impl IconExtWindows for Icon {
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_resource_name(name, size)?;
Ok(Icon { inner: win_icon })
}
}

View File

@@ -81,9 +81,7 @@ pub type XWindow = u32;
#[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
/// Additional methods on [`ActiveEventLoop`] that are specific to X11.
@@ -105,7 +103,7 @@ pub trait EventLoopExtX11 {
fn is_x11(&self) -> bool;
}
impl EventLoopExtX11 for EventLoop {
impl<T: 'static> EventLoopExtX11 for EventLoop<T> {
#[inline]
fn is_x11(&self) -> bool {
!self.event_loop.is_wayland()
@@ -124,7 +122,7 @@ pub trait EventLoopBuilderExtX11 {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl EventLoopBuilderExtX11 for EventLoopBuilder {
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);

View File

@@ -3,7 +3,7 @@ use std::collections::VecDeque;
use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::sync::{mpsc, Arc, Mutex};
use std::time::{Duration, Instant};
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
@@ -12,13 +12,12 @@ use android_activity::{
};
use tracing::{debug, trace, warn};
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error;
use crate::error::EventLoopError;
use crate::event::{self, Force, InnerSizeWriter, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
use crate::event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Fullscreen;
use crate::window::{
@@ -42,6 +41,41 @@ fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
struct PeekableReceiver<T> {
recv: mpsc::Receiver<T>,
first: Option<T>,
}
impl<T> PeekableReceiver<T> {
pub fn from_recv(recv: mpsc::Receiver<T>) -> Self {
Self { recv, first: None }
}
pub fn has_incoming(&mut self) -> bool {
if self.first.is_some() {
return true;
}
match self.recv.try_recv() {
Ok(v) => {
self.first = Some(v);
true
},
Err(mpsc::TryRecvError::Empty) => false,
Err(mpsc::TryRecvError::Disconnected) => {
warn!("Channel was disconnected when checking incoming");
false
},
}
}
pub fn try_recv(&mut self) -> Result<T, mpsc::TryRecvError> {
if let Some(first) = self.first.take() {
return Ok(first);
}
self.recv.try_recv()
}
}
#[derive(Clone)]
struct SharedFlagSetter {
flag: Arc<AtomicBool>,
@@ -97,12 +131,13 @@ impl RedrawRequester {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop {
android_app: AndroidApp,
pub struct EventLoop<T: 'static> {
pub(crate) android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag,
proxy_wake_up: Arc<AtomicBool>,
loop_running: bool, // Dispatched `NewEvents<Init>`
user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, // must wake looper whenever something gets sent
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool,
pending_redraw: bool,
cause: StartCause,
@@ -122,11 +157,11 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
}
impl EventLoop {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let (user_events_sender, user_events_receiver) = mpsc::channel();
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
@@ -149,7 +184,8 @@ impl EventLoop {
_marker: PhantomData,
},
redraw_flag,
proxy_wake_up,
user_events_sender,
user_events_receiver: PeekableReceiver::from_recv(user_events_receiver),
loop_running: false,
running: false,
pending_redraw: false,
@@ -159,28 +195,27 @@ impl EventLoop {
})
}
fn single_iteration<A: ApplicationHandler>(
&mut self,
main_event: Option<MainEvent<'_>>,
app: &mut A,
) {
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
trace!("Mainloop iteration");
let cause = self.cause;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
app.new_events(self.window_target(), cause);
callback(event::Event::NewEvents(cause), self.window_target());
if let Some(event) = main_event {
trace!("Handling main event {:?}", event);
match event {
MainEvent::InitWindow { .. } => {
app.resumed(self.window_target());
callback(event::Event::Resumed, self.window_target());
},
MainEvent::TerminateWindow { .. } => {
app.suspended(self.window_target());
callback(event::Event::Suspended, self.window_target());
},
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
@@ -189,15 +224,23 @@ impl EventLoop {
},
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(true);
app.window_event(self.window_target(), window_id, event);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(true),
},
self.window_target(),
);
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(false);
app.window_event(self.window_target(), window_id, event);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(false),
},
self.window_target(),
);
},
MainEvent::ConfigChanged { .. } => {
let monitor = MonitorHandle::new(self.android_app.clone());
@@ -207,19 +250,20 @@ impl EventLoop {
let new_inner_size = Arc::new(Mutex::new(
MonitorHandle::new(self.android_app.clone()).size(),
));
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
},
};
app.window_event(self.window_target(), window_id, event);
callback(event, self.window_target());
}
},
MainEvent::LowMemory => {
app.memory_warning(self.window_target());
callback(event::Event::MemoryWarning, self.window_target());
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
@@ -267,7 +311,7 @@ impl EventLoop {
match android_app.input_events_iter() {
Ok(mut input_iter) => loop {
let read_event =
input_iter.next(|event| self.handle_input_event(&android_app, event, app));
input_iter.next(|event| self.handle_input_event(&android_app, event, callback));
if !read_event {
break;
@@ -278,8 +322,11 @@ impl EventLoop {
},
}
if self.proxy_wake_up.swap(false, Ordering::Relaxed) {
app.proxy_wake_up(self.window_target());
// Empty the user event buffer
{
while let Ok(event) = self.user_events_receiver.try_recv() {
callback(crate::event::Event::UserEvent(event), self.window_target());
}
}
if self.running {
@@ -291,32 +338,39 @@ impl EventLoop {
} else {
PhysicalSize::new(0, 0)
};
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Resized(size);
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Resized(size),
};
callback(event, self.window_target());
}
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::RedrawRequested;
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::RedrawRequested,
};
callback(event, self.window_target());
}
}
// This is always the last event we dispatch before poll again
app.about_to_wait(self.window_target());
callback(event::Event::AboutToWait, self.window_target());
self.pending_redraw = pending_redraw;
}
fn handle_input_event<A: ApplicationHandler>(
fn handle_input_event<F>(
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
app: &mut A,
) -> InputStatus {
callback: &mut F,
) -> InputStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
@@ -354,16 +408,17 @@ impl EventLoop {
"Input event {device_id:?}, {phase:?}, loc={location:?}, \
pointer={pointer:?}"
);
let event = event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
});
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
}),
};
callback(event, self.window_target());
}
}
},
@@ -391,22 +446,23 @@ impl EventLoop {
&mut self.combining_accent,
);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
is_synthetic: false,
};
app.window_event(self.window_target(), window_id, event);
callback(event, self.window_target());
},
}
},
@@ -418,16 +474,19 @@ impl EventLoop {
input_status
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
self.run_on_demand(event_handler)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -441,11 +500,10 @@ impl EventLoop {
}
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
if !self.loop_running {
self.loop_running = true;
@@ -456,18 +514,18 @@ impl EventLoop {
self.cause = StartCause::Init;
// run the initial loop iteration
self.single_iteration(None, app);
self.single_iteration(None, &mut callback);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if self.exiting() {
self.loop_running = false;
app.exiting(self.window_target());
callback(event::Event::LoopExiting, self.window_target());
PumpStatus::Exit(0)
} else {
@@ -475,34 +533,32 @@ impl EventLoop {
}
}
fn poll_events_with_timeout<A: ApplicationHandler>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset();
timeout = if self.running
&& (self.pending_redraw || self.proxy_wake_up.load(Ordering::Relaxed))
{
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
timeout =
if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) {
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
min_timeout(control_flow_timeout, timeout)
};
min_timeout(control_flow_timeout, timeout)
};
let android_app = self.android_app.clone(); // Don't borrow self as part of poll expression
android_app.poll_events(timeout, |poll_event| {
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
app.poll_events(timeout, |poll_event| {
let mut main_event = None;
match poll_event {
@@ -517,7 +573,7 @@ impl EventLoop {
// We also ignore wake ups while suspended.
self.pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running
|| (!self.pending_redraw && !self.proxy_wake_up.load(Ordering::Relaxed))
|| (!self.pending_redraw && !self.user_events_receiver.has_incoming())
{
return;
}
@@ -543,7 +599,7 @@ impl EventLoop {
},
};
self.single_iteration(main_event, app);
self.single_iteration(main_event, &mut callback);
});
}
@@ -551,9 +607,9 @@ impl EventLoop {
&self.window_target
}
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
proxy_wake_up: self.proxy_wake_up.clone(),
user_events_sender: self.user_events_sender.clone(),
waker: self.android_app.create_waker(),
}
}
@@ -567,21 +623,30 @@ impl EventLoop {
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
pub struct EventLoopProxy<T: 'static> {
user_events_sender: mpsc::Sender<T>,
waker: AndroidAppWaker,
}
impl EventLoopProxy {
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, Ordering::Relaxed);
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
waker: self.waker.clone(),
}
}
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender.send(event).map_err(|err| event_loop::EventLoopClosed(err.0))?;
self.waker.wake();
Ok(())
}
}
pub struct ActiveEventLoop {
app: AndroidApp,
pub(crate) app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
@@ -612,6 +677,11 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -838,7 +908,13 @@ impl Window {
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
pub fn set_ime_allowed(&self, _allowed: bool) {}
pub fn set_ime_allowed(&self, allowed: bool) {
if allowed {
self.app.show_soft_input(true);
} else {
self.app.hide_soft_input(true);
}
}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}

View File

@@ -1,106 +0,0 @@
#![allow(clippy::unnecessary_cast)]
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_state::ApplicationDelegate;
use super::DEVICE_ID;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
pub(super) struct WinitApplication;
unsafe impl ClassType for WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
impl DeclaredClass for WinitApplication {}
unsafe impl WinitApplication {
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
#[method(sendEvent:)]
fn send_event(&self, event: &NSEvent) {
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventType::KeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
{
if let Some(key_window) = self.keyWindow() {
key_window.sendEvent(event);
}
} else {
let delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
maybe_dispatch_device_event(&delegate, event);
unsafe { msg_send![super(self), sendEvent: event] }
}
}
}
);
fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) {
let event_type = unsafe { event.r#type() };
#[allow(non_upper_case_globals)]
match event_type {
NSEventType::MouseMoved
| NSEventType::LeftMouseDragged
| NSEventType::OtherMouseDragged
| NSEventType::RightMouseDragged => {
let delta_x = unsafe { event.deltaX() } as f64;
let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 {
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Motion {
axis: 0,
value: delta_x,
});
});
}
if delta_y != 0.0 {
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Motion {
axis: 1,
value: delta_y,
});
})
}
if delta_x != 0.0 || delta_y != 0.0 {
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
delta: (delta_x, delta_y),
});
});
}
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Pressed,
});
});
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Released,
});
});
},
_ => (),
}
}

View File

@@ -1,11 +0,0 @@
//! Apple/Darwin-specific implementations
#[cfg(target_os = "macos")]
mod appkit;
#[cfg(not(target_os = "macos"))]
mod uikit;
#[cfg(target_os = "macos")]
pub use self::appkit::*;
#[cfg(not(target_os = "macos"))]
pub use self::uikit::*;

View File

@@ -1,60 +0,0 @@
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_ui_kit::UIApplication;
use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper};
use crate::event::Event;
declare_class!(
pub struct AppDelegate;
unsafe impl ClassType for AppDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for AppDelegate {}
// UIApplicationDelegate protocol
unsafe impl AppDelegate {
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
true
}
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
}
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
}
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
app_state::terminated(application);
}
#[method(applicationDidReceiveMemoryWarning:)]
fn did_receive_memory_warning(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
}
}
);

View File

@@ -10,7 +10,7 @@ use std::{fmt, mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopDefaultMode, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::rc::Retained;
@@ -40,9 +40,12 @@ macro_rules! bug_assert {
};
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event, &RootActiveEventLoop)>,
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
pub(crate) event_loop: RootActiveEventLoop,
}
@@ -56,14 +59,14 @@ impl fmt::Debug for EventLoopHandler {
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event) {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
(self.handler)(event, &self.event_loop)
}
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event),
StaticEvent(Event<HandlePendingUserEvents>),
ScaleFactorChanged(ScaleFactorChanged),
}
@@ -85,7 +88,7 @@ enum UserCallbackTransitionResult<'a> {
},
}
impl Event {
impl Event<HandlePendingUserEvents> {
fn is_redraw(&self) -> bool {
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
}
@@ -144,6 +147,8 @@ impl AppState {
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
#[allow(unknown_lints)] // New lint below
#[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead.
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
@@ -370,6 +375,7 @@ impl AppState {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.waker.stop()
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant =>
@@ -622,7 +628,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
drop(this);
handler.handle_event(Event::UserWakeUp);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
loop {
let mut this = AppState::get_mut(mtm);
@@ -655,7 +661,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}
handler.handle_event(Event::UserWakeUp);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
}
}
@@ -802,7 +808,7 @@ impl EventLoopWaker {
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopDefaultMode);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
EventLoopWaker { timer }
}

View File

@@ -2,30 +2,37 @@ use std::collections::VecDeque;
use std::ffi::{c_char, c_int, c_void};
use std::marker::PhantomData;
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::sync::mpsc::{self, Receiver, Sender};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopDefaultMode, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
UIApplicationWillTerminateNotification, UIDevice, UIScreen, UIUserInterfaceIdiom,
};
use super::app_state::EventLoopHandler;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
use crate::window::{CustomCursor, CustomCursorSource};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::platform::ios::Idiom;
use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents};
use crate::window::{CustomCursor, CustomCursorSource, Theme};
use super::app_delegate::AppDelegate;
use super::app_state::AppState;
use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper};
use super::notification_center::create_observer;
use super::{app_state, monitor, MonitorHandle};
#[derive(Debug)]
@@ -57,6 +64,11 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -107,44 +119,46 @@ impl OwnedDisplayHandle {
}
}
fn map_user_event<A: ApplicationHandler>(
app: &mut A,
proxy_wake_up: Arc<AtomicBool>,
) -> impl FnMut(Event, &RootActiveEventLoop) + '_ {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserWakeUp => {
if proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
app.proxy_wake_up(window_target);
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop),
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
},
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
}
}
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
proxy_wake_up: Arc<AtomicBool>,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootActiveEventLoop,
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
// system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>,
_did_become_active_observer: Retained<NSObject>,
_will_resign_active_observer: Retained<NSObject>,
_will_enter_foreground_observer: Retained<NSObject>,
_did_enter_background_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
_did_receive_memory_warning_observer: Retained<NSObject>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop, EventLoopError> {
) -> Result<EventLoop<T>, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
@@ -157,19 +171,109 @@ impl EventLoop {
SINGLETON_INIT = true;
}
let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let center = unsafe { NSNotificationCenter::defaultCenter() };
let _did_finish_launching_observer = create_observer(
&center,
// `application:didFinishLaunchingWithOptions:`
unsafe { UIApplicationDidFinishLaunchingNotification },
move |_| {
app_state::did_finish_launching(mtm);
},
);
let _did_become_active_observer = create_observer(
&center,
// `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
},
);
let _will_resign_active_observer = create_observer(
&center,
// `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification },
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
},
);
let _will_enter_foreground_observer = create_observer(
&center,
// `applicationWillEnterForeground:`
unsafe { UIApplicationWillEnterForegroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, false);
},
);
let _did_enter_background_observer = create_observer(
&center,
// `applicationDidEnterBackground:`
unsafe { UIApplicationDidEnterBackgroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, true);
},
);
let _will_terminate_observer = create_observer(
&center,
// `applicationWillTerminate:`
unsafe { UIApplicationWillTerminateNotification },
move |notification| {
let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object");
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
// (somewhat) documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
app_state::terminated(&app);
},
);
let _did_receive_memory_warning_observer = create_observer(
&center,
// `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| {
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::MemoryWarning),
);
},
);
Ok(EventLoop {
mtm,
proxy_wake_up,
sender,
receiver,
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
_did_finish_launching_observer,
_did_become_active_observer,
_will_resign_active_observer,
_will_enter_foreground_observer,
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
})
}
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> ! {
pub fn run<F>(self, handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
@@ -179,12 +283,12 @@ impl EventLoop {
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(app, self.proxy_wake_up.clone());
let handler = map_user_event(handler, self.receiver);
let handler = unsafe {
std::mem::transmute::<
Box<dyn FnMut(Event, &RootActiveEventLoop)>,
Box<dyn FnMut(Event, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
>(Box::new(handler))
};
@@ -192,9 +296,6 @@ impl EventLoop {
app_state::will_launch(self.mtm, handler);
// Ensure application delegate is initialized
let _ = AppDelegate::class();
extern "C" {
// These functions are in crt_externs.h.
fn _NSGetArgc() -> *mut c_int;
@@ -205,15 +306,17 @@ impl EventLoop {
UIApplicationMain(
*_NSGetArgc(),
NonNull::new(*_NSGetArgv()).unwrap(),
// We intentionally override neither the application nor the delegate, to allow the
// user to do so themselves!
None,
None,
Some(&NSString::from_str(AppDelegate::NAME)),
)
};
unreachable!()
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
@@ -221,21 +324,35 @@ impl EventLoop {
}
}
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Clone for EventLoopProxy {
fn clone(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
match UIDevice::currentDevice(self.mtm).userInterfaceIdiom() {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => Idiom::Unspecified,
}
}
}
impl Drop for EventLoopProxy {
pub struct EventLoopProxy<T> {
sender: Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
}
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
@@ -244,8 +361,8 @@ impl Drop for EventLoopProxy {
}
}
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> EventLoopProxy {
impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -266,21 +383,22 @@ impl EventLoopProxy {
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { proxy_wake_up, source }
EventLoopProxy { sender, source }
}
}
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}

View File

@@ -1,9 +1,9 @@
#![allow(clippy::let_unit_value)]
mod app_delegate;
mod app_state;
mod event_loop;
mod monitor;
mod notification_center;
mod view;
mod view_controller;
mod window;
@@ -32,7 +32,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}

View File

@@ -9,9 +9,9 @@ use objc2::Message;
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2_ui_kit::{UIScreen, UIScreenMode};
use super::app_state;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
use crate::platform_impl::platform::app_state;
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
@@ -102,13 +102,20 @@ impl Clone for MonitorHandle {
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(self as *const Self).hash(state);
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other)
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
ptr::eq(
Retained::as_ptr(self.ui_screen.get(mtm)),
Retained::as_ptr(other.ui_screen.get(mtm)),
)
}
}
@@ -122,8 +129,10 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: Only getting the pointer.
// TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self))
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
}
}
@@ -242,3 +251,27 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
#[allow(deprecated)]
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
}
#[cfg(test)]
mod tests {
use objc2_foundation::NSSet;
use super::*;
// Test that UIScreen pointer comparisons are correct.
#[test]
#[allow(deprecated)]
fn screen_comparisons() {
// Test code, doesn't matter that it's not thread safe
let mtm = unsafe { MainThreadMarker::new_unchecked() };
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
});
}
}

View File

@@ -0,0 +1,27 @@
use std::ptr::NonNull;
use block2::RcBlock;
use objc2::rc::Retained;
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
/// Observe the given notification.
///
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
/// give the user full control over those.
pub fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,
) -> Retained<NSObject> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() });
});
unsafe {
center.addObserverForName_object_queue_usingBlock(
Some(name),
None, // No sender filter
None, // No queue, run on posting thread (i.e. main thread)
&block,
)
}
}

View File

@@ -4,19 +4,21 @@ use std::cell::{Cell, RefCell};
use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
use objc2_ui_kit::{
UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIPanGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer,
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
};
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use super::DEVICE_ID;
use crate::dpi::PhysicalPosition;
use crate::event::{Event, Force, Touch, TouchPhase, WindowEvent};
use crate::event::{ElementState, Event, Force, KeyEvent, Touch, TouchPhase, WindowEvent};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::platform_impl::platform::DEVICE_ID;
use crate::platform_impl::KeyEventExtra;
use crate::window::{WindowAttributes, WindowId as RootWindowId};
pub struct WinitViewState {
@@ -188,7 +190,7 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.scale())
}
state => panic!("unexpected recognizer state: {:?}", state),
state => panic!("unexpected recognizer state: {state:?}"),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
@@ -247,7 +249,7 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.rotation())
}
state => panic!("unexpected recognizer state: {:?}", state),
state => panic!("unexpected recognizer state: {state:?}"),
};
// Make delta negative to match macos, convert to degrees
@@ -298,7 +300,7 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
}
state => panic!("unexpected recognizer state: {:?}", state),
state => panic!("unexpected recognizer state: {state:?}"),
};
@@ -314,6 +316,11 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(canBecomeFirstResponder)]
fn can_become_first_responder(&self) -> bool {
true
}
}
unsafe impl NSObjectProtocol for WinitView {}
@@ -324,6 +331,26 @@ declare_class!(
true
}
}
unsafe impl UITextInputTraits for WinitView {
}
unsafe impl UIKeyInput for WinitView {
#[method(hasText)]
fn has_text(&self) -> bool {
true
}
#[method(insertText:)]
fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text)
}
#[method(deleteBackward)]
fn delete_backward(&self) {
self.handle_delete_backward()
}
}
);
impl WinitView {
@@ -512,4 +539,69 @@ impl WinitView {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, touch_events);
}
fn handle_insert_text(&self, text: &NSString) {
let window = self.window().unwrap();
let window_id = RootWindowId(window.id());
let mtm = MainThreadMarker::new().unwrap();
// send individual events for each character
app_state::handle_nonuser_events(
mtm,
text.to_string().chars().flat_map(|c| {
let text = smol_str::SmolStr::from_iter([c]);
// Emit both press and release events
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
event: KeyEvent {
text: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(
NativeKeyCode::Unidentified,
),
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
device_id: DEVICE_ID,
},
})
})
}),
);
}
fn handle_delete_backward(&self) {
let window = self.window().unwrap();
let window_id = RootWindowId(window.id());
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(
mtm,
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: KeyEvent {
state,
logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace),
platform_specific: KeyEventExtra {},
repeat: false,
location: KeyLocation::Standard,
text: None,
},
is_synthetic: false,
},
})
}),
);
}
}

View File

@@ -17,13 +17,15 @@ use tracing::{debug, warn};
use super::app_state::EventWrapper;
use super::view::WinitView;
use super::view_controller::WinitViewController;
use super::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle};
use crate::cursor::Cursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Event, WindowEvent};
use crate::icon::Icon;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::platform_impl::platform::{
app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle,
};
use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
@@ -365,12 +367,24 @@ impl Inner {
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
}
pub fn set_ime_allowed(&self, _allowed: bool) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
/// Show / hide the keyboard. To show the keyboard, we call `becomeFirstResponder`,
/// requesting focus for the [WinitView]. Since [WinitView] implements
/// [objc2_ui_kit::UIKeyInput], the keyboard will be shown.
/// <https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder>
pub fn set_ime_allowed(&self, allowed: bool) {
if allowed {
unsafe {
self.view.becomeFirstResponder();
}
} else {
unsafe {
self.view.resignFirstResponder();
}
}
}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
warn!("`Window::set_ime_purpose` is ignored on iOS")
}
pub fn focus_window(&self) {
@@ -699,7 +713,7 @@ pub struct WindowId {
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
WindowId { window: std::ptr::null_mut() }
}
}

View File

@@ -184,7 +184,7 @@ pub struct KeyContext<'a> {
scratch_buffer: &'a mut Vec<u8>,
}
impl<'a> KeyContext<'a> {
impl KeyContext<'_> {
pub fn process_key_event(
&mut self,
keycode: u32,
@@ -320,7 +320,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the oppsite behaviour.
// if someone specifically wants the opposite behaviour.
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() {
Ok(text) => text,

View File

@@ -11,8 +11,6 @@ use std::{env, fmt};
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use crate::application::ApplicationHandler;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::utils::Lazy;
use smol_str::SmolStr;
@@ -21,9 +19,12 @@ use smol_str::SmolStr;
use self::x11::{X11Error, XConnection, XError, XNotSupported};
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event_loop::{AsyncRequestSerial, ControlFlow, DeviceEvents};
use crate::event_loop::{
ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::icon::Icon;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
use crate::window::{
@@ -116,6 +117,8 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)]
XNotSupported(XNotSupported),
#[cfg(x11_platform)]
XError(Arc<X11Error>),
#[cfg(wayland_platform)]
WaylandError(Arc<wayland::WaylandError>),
@@ -126,6 +129,8 @@ impl fmt::Display for OsError {
match *self {
OsError::Misc(e) => _f.pad(e),
#[cfg(x11_platform)]
OsError::XNotSupported(ref e) => fmt::Display::fmt(e, _f),
#[cfg(x11_platform)]
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
#[cfg(wayland_platform)]
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
@@ -156,7 +161,7 @@ impl From<u64> for WindowId {
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}
@@ -170,11 +175,11 @@ pub enum DeviceId {
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
return DeviceId::X(x11::DeviceId::dummy());
}
}
@@ -642,18 +647,18 @@ pub(crate) enum PlatformCustomCursor {
/// Hooks for X11 errors.
#[cfg(x11_platform)]
pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
#[cfg(x11_platform)]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap();
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
@@ -691,22 +696,28 @@ unsafe extern "C" fn x_error_callback(
0
}
pub enum EventLoop {
#[allow(clippy::large_enum_variant)]
pub enum EventLoop<T: 'static> {
#[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop>),
Wayland(Box<wayland::EventLoop<T>>),
#[cfg(x11_platform)]
X(x11::EventLoop),
X(x11::EventLoop<T>),
}
#[derive(Clone)]
pub enum EventLoopProxy {
pub enum EventLoopProxy<T: 'static> {
#[cfg(x11_platform)]
X(x11::EventLoopProxy),
X(x11::EventLoopProxy<T>),
#[cfg(wayland_platform)]
Wayland(wayland::EventLoopProxy),
Wayland(wayland::EventLoopProxy<T>),
}
impl EventLoop {
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy)
}
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
@@ -758,22 +769,24 @@ impl EventLoop {
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
Backend::Wayland => EventLoop::new_wayland_any_thread(),
#[cfg(x11_platform)]
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
Backend::X => EventLoop::new_x11_any_thread(),
}
}
#[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop, EventLoopError> {
fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
Ok(xconn) => xconn.clone(),
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
Err(err) => {
return Err(EventLoopError::Os(os_error!(OsError::XNotSupported(err.clone()))))
},
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -789,27 +802,29 @@ impl EventLoop {
}
}
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
self.run_on_demand(callback)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
pub fn window_target(&self) -> &crate::event_loop::ActiveEventLoop {
@@ -817,24 +832,25 @@ impl EventLoop {
}
}
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
}
}
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
}
}
impl EventLoopProxy {
pub fn wake_up(&self) {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.wake_up())
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
}
}
#[allow(clippy::large_enum_variant)]
pub enum ActiveEventLoop {
#[cfg(wayland_platform)]
Wayland(wayland::ActiveEventLoop),
@@ -889,6 +905,11 @@ impl ActiveEventLoop {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

@@ -4,16 +4,22 @@ use std::cell::{Cell, RefCell};
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::os::fd::OwnedFd;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use calloop::ping::Ping;
use rustix::event::{PollFd, PollFlags};
use rustix::pipe::{self, PipeFlags};
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use tracing::warn;
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError as RootOsError};
@@ -39,7 +45,7 @@ use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop.
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
loop_running: bool,
@@ -47,8 +53,13 @@ pub struct EventLoop {
compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>,
/// Event loop proxy
event_loop_proxy: EventLoopProxy,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
// we don't really want, since it'll break public API by a lot.
/// Pending events from the user.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// The Wayland dispatcher to has raw access to the queue when needed, such as
/// when creating a new window.
@@ -63,10 +74,12 @@ pub struct EventLoop {
// XXX drop after everything else, just to be safe.
/// Calloop's event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
pump_event_notifier: Option<PumpEventNotifier>,
}
impl EventLoop {
pub fn new() -> Result<EventLoop, EventLoopError> {
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, EventLoopError> {
macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
@@ -109,12 +122,16 @@ impl EventLoop {
)?;
// Setup the user proxy.
let (ping, ping_source) = calloop::ping::make_ping().unwrap();
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
let result = event_loop
.handle()
.insert_source(ping_source, move |_, _, winit_state: &mut WinitState| {
winit_state.dispatched_events = true;
winit_state.proxy_wake_up = true;
.insert_source(user_events_channel, move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
})
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
@@ -152,27 +169,25 @@ impl EventLoop {
window_ids: Vec::new(),
connection,
wayland_dispatcher,
event_loop_proxy: EventLoopProxy::new(ping),
user_events_sender,
pending_user_events,
event_loop,
window_target: RootActiveEventLoop {
p: PlatformActiveEventLoop::Wayland(window_target),
_marker: PhantomData,
},
pump_event_notifier: None,
};
Ok(event_loop)
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let exit = loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -194,39 +209,58 @@ impl EventLoop {
exit
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
if !self.loop_running {
self.loop_running = true;
// Run the initial loop iteration.
self.single_iteration(app, StartCause::Init);
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(&self.window_target);
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
// NOTE: spawn a wake-up thread, thus if we have code reading the wayland connection
// in parallel to winit, we ensure that the loop itself is marked as having events.
if timeout.is_some() && self.pump_event_notifier.is_none() {
let awakener = match &self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => {
window_target.event_loop_awakener.clone()
},
#[cfg(x11_platform)]
PlatformActiveEventLoop::X(_) => unreachable!(),
};
self.pump_event_notifier =
Some(PumpEventNotifier::spawn(self.connection.clone(), awakener));
}
if let Some(pump_event_notifier) = self.pump_event_notifier.as_ref() {
// Notify that we don't have to wait, since we're out of winit.
*pump_event_notifier.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
pump_event_notifier.control.1.notify_one();
}
PumpStatus::Continue
}
}
pub fn poll_events_with_timeout<A: ApplicationHandler>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let cause = loop {
let start = Instant::now();
@@ -281,17 +315,23 @@ impl EventLoop {
// Reduce spurious wake-ups.
let dispatched_events = self.with_state(|state| state.dispatched_events);
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
if matches!(cause, StartCause::WaitCancelled { .. })
&& !dispatched_events
&& timeout.is_none()
{
continue;
}
break cause;
};
self.single_iteration(app, cause);
self.single_iteration(&mut callback, cause);
}
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
// NOTE currently just indented to simplify the diff
// We retain these grow-only scratch buffers as part of the EventLoop
@@ -302,17 +342,18 @@ impl EventLoop {
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
app.new_events(&self.window_target, cause);
callback(Event::NewEvents(cause), &self.window_target);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
app.resumed(&self.window_target);
callback(Event::Resumed, &self.window_target);
}
// Indicate user wake up.
if self.with_state(|state| mem::take(&mut state.proxy_wake_up)) {
app.proxy_wake_up(&self.window_target);
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in self.pending_user_events.borrow_mut().drain(..) {
callback(Event::UserEvent(user_event), &self.window_target);
}
// Drain the pending compositor updates.
@@ -333,13 +374,18 @@ impl EventLoop {
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
let root_window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
};
app.window_event(&self.window_target, root_window_id, event);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
},
&self.window_target,
);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
@@ -382,14 +428,23 @@ impl EventLoop {
size
});
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::Resized(physical_size);
app.window_event(&self.window_target, window_id, event);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
);
}
if compositor_update.close_window {
let window_id = crate::window::WindowId(window_id);
app.window_event(&self.window_target, window_id, WindowEvent::CloseRequested);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
);
}
}
@@ -398,15 +453,8 @@ impl EventLoop {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Handle non-synthetic events.
@@ -414,15 +462,8 @@ impl EventLoop {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Collect the window ids
@@ -458,8 +499,10 @@ impl EventLoop {
});
if let Some(event) = event {
let window_id = crate::window::WindowId(*window_id);
app.window_event(&self.window_target, window_id, event);
callback(
Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event },
&self.window_target,
);
}
}
@@ -469,7 +512,7 @@ impl EventLoop {
});
// This is always the last event we dispatch before poll again
app.about_to_wait(&self.window_target);
callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;
@@ -513,8 +556,8 @@ impl EventLoop {
}
#[inline]
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop_proxy.clone()
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.user_events_sender.clone())
}
#[inline]
@@ -576,13 +619,13 @@ impl EventLoop {
}
}
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
@@ -590,7 +633,7 @@ impl AsRawFd for EventLoop {
pub struct ActiveEventLoop {
/// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping,
pub event_loop_awakener: Ping,
/// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>,
@@ -674,3 +717,94 @@ impl ActiveEventLoop {
.into())
}
}
#[derive(Debug)]
struct PumpEventNotifier {
/// Whether we're in winit or not.
control: Arc<(Mutex<PumpEventNotifierAction>, Condvar)>,
/// Waker handle for the working thread.
worker_waker: Option<OwnedFd>,
/// Thread handle.
handle: Option<JoinHandle<()>>,
}
impl Drop for PumpEventNotifier {
fn drop(&mut self) {
// Wake-up the thread.
if let Some(worker_waker) = self.worker_waker.as_ref() {
let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]);
}
*self.control.0.lock().unwrap() = PumpEventNotifierAction::Shutdown;
self.control.1.notify_one();
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
impl PumpEventNotifier {
fn spawn(connection: Connection, awakener: Ping) -> Self {
// Start from the waiting state.
let control = Arc::new((Mutex::new(PumpEventNotifierAction::Pause), Condvar::new()));
let control_thread = Arc::clone(&control);
let (read, write) = match pipe::pipe_with(PipeFlags::CLOEXEC | PipeFlags::NONBLOCK) {
Ok((read, write)) => (read, write),
Err(_) => return Self { control, handle: None, worker_waker: None },
};
let handle =
std::thread::Builder::new().name(String::from("pump_events mon")).spawn(move || {
let (lock, cvar) = &*control_thread;
'outer: loop {
let mut wait = lock.lock().unwrap();
while *wait == PumpEventNotifierAction::Pause {
wait = cvar.wait(wait).unwrap();
}
// Exit the loop when we're asked to. Given that we poll
// only once we can take the `prepare_read`, but in some cases
// it could be not possible, we may block on `join`.
if *wait == PumpEventNotifierAction::Shutdown {
break 'outer;
}
// Wake-up the main loop and put this one back to sleep.
*wait = PumpEventNotifierAction::Pause;
drop(wait);
while let Some(read_guard) = connection.prepare_read() {
let _ = connection.flush();
let poll_fd = PollFd::from_borrowed_fd(connection.as_fd(), PollFlags::IN);
let pipe_poll_fd = PollFd::from_borrowed_fd(read.as_fd(), PollFlags::IN);
// Read from the `fd` before going back to poll.
if Ok(1) == rustix::io::read(read.as_fd(), &mut [0u8; 1]) {
break 'outer;
}
let _ = rustix::event::poll(&mut [poll_fd, pipe_poll_fd], -1);
// Non-blocking read the connection.
let _ = read_guard.read_without_dispatch();
}
awakener.ping();
}
});
if let Some(err) = handle.as_ref().err() {
warn!("failed to spawn pump_events wake-up thread: {err}");
}
PumpEventNotifier { control, handle: handle.ok(), worker_waker: Some(write) }
}
}
#[derive(Debug, PartialEq, Eq)]
enum PumpEventNotifierAction {
/// Monitor the wayland queue.
Monitor,
/// Pause monitoring.
Pause,
/// Shutdown the thread.
Shutdown,
}

View File

@@ -1,19 +1,28 @@
//! An event loop proxy.
use sctk::reexports::calloop::ping::Ping;
use std::sync::mpsc::SendError;
use sctk::reexports::calloop::channel::Sender;
use crate::event_loop::EventLoopClosed;
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
pub struct EventLoopProxy<T: 'static> {
user_events_sender: Sender<T>,
}
impl EventLoopProxy {
pub fn new(ping: Ping) -> Self {
Self { ping }
}
pub fn wake_up(&self) {
self.ping.ping();
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy { user_events_sender: self.user_events_sender.clone() }
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn new(user_events_sender: Sender<T>) -> Self {
Self { user_events_sender }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_events_sender.send(event).map_err(|SendError(error)| EventLoopClosed(error))
}
}

View File

@@ -12,7 +12,7 @@ use super::{DeviceId, WindowId};
/// to the winit's user.
#[derive(Default)]
pub struct EventSink {
pub(crate) window_events: Vec<Event>,
pub window_events: Vec<Event<()>>,
}
impl EventSink {
@@ -47,7 +47,7 @@ impl EventSink {
}
#[inline]
pub(crate) fn drain(&mut self) -> Drain<'_, Event> {
pub fn drain(&mut self) -> Drain<'_, Event<()>> {
self.window_events.drain(..)
}
}

View File

@@ -66,7 +66,7 @@ impl From<WaylandError> for OsError {
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}

View File

@@ -18,7 +18,6 @@ use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::seat::WinitSeatState;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
@@ -33,7 +32,17 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
) {
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return,
None => {
warn!("Received keyboard event {event:?} without seat");
return;
},
};
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => {
warn!("Received keyboard event {event:?} without keyboard");
return;
},
};
match event {
@@ -43,7 +52,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
warn!("non-xkb compatible keymap")
},
WlKeymapFormat::XkbV1 => {
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let context = &mut keyboard_state.xkb_context;
context.set_keymap_from_fd(fd, size as usize);
},
_ => unreachable!(),
@@ -67,7 +76,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
};
// Drop the repeat, if there were any.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -93,7 +101,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
// NOTE: we should drop the repeat regardless whethere it was for the present
// window of for the window which just went gone.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -128,7 +135,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let key = key + 8;
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
key,
@@ -136,7 +143,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let delay = match keyboard_state.repeat_info {
RepeatInfo::Repeat { delay, .. } => delay,
RepeatInfo::Disable => return,
@@ -163,18 +169,25 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
state.dispatched_events = true;
let data = wl_keyboard.data::<KeyboardData>().unwrap();
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to
// `None` is instant.
let repeat_keycode =
match seat_state.keyboard_state.as_ref().unwrap().current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to `None`
// is instant.
let repeat_keycode = match keyboard_state.current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
repeat_keycode,
@@ -183,7 +196,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
);
// NOTE: the gap could change dynamically while repeat is going.
match seat_state.keyboard_state.as_ref().unwrap().repeat_info {
match keyboard_state.repeat_info {
RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap),
RepeatInfo::Disable => TimeoutAction::Drop,
}
@@ -194,7 +207,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let key = key + 8;
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
key,
@@ -202,7 +215,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
if keyboard_state.repeat_info != RepeatInfo::Disable
&& keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key)
&& Some(key) == keyboard_state.current_repeat
@@ -216,7 +228,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
WlKeyboardEvent::Modifiers {
mods_depressed, mods_latched, mods_locked, group, ..
} => {
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let xkb_context = &mut keyboard_state.xkb_context;
let xkb_state = match xkb_context.state_mut() {
Some(state) => state,
None => return,
@@ -240,7 +252,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
);
},
WlKeyboardEvent::RepeatInfo { rate, delay } => {
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.repeat_info = if rate == 0 {
// Stop the repeat once we get a disable event.
keyboard_state.current_repeat = None;
@@ -348,7 +359,7 @@ impl KeyboardData {
}
fn key_input(
seat_state: &mut WinitSeatState,
keyboard_state: &mut KeyboardState,
event_sink: &mut EventSink,
data: &KeyboardData,
keycode: u32,
@@ -360,8 +371,6 @@ fn key_input(
None => return,
};
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
let event = key_context.process_key_event(keycode, state, repeat);

View File

@@ -3,6 +3,7 @@
use std::sync::Arc;
use ahash::AHashMap;
use tracing::warn;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -76,7 +77,13 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::new_capability for unknown seat");
return;
},
};
match capability {
SeatCapability::Touch if seat_state.touch.is_none() => {
@@ -89,8 +96,12 @@ impl SeatHandler for WinitState {
},
SeatCapability::Pointer if seat_state.pointer.is_none() => {
let surface = self.compositor_state.create_surface(queue_handle);
let viewport = self
.viewporter_state
.as_ref()
.map(|state| state.get_viewport(&surface, queue_handle));
let surface_id = surface.id();
let pointer_data = WinitPointerData::new(seat.clone());
let pointer_data = WinitPointerData::new(seat.clone(), viewport);
let themed_pointer = self
.seat_state
.get_pointer_with_theme_and_data(
@@ -139,7 +150,13 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::remove_capability for unknown seat");
return;
},
};
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();

View File

@@ -4,6 +4,8 @@ use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::warn;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -16,6 +18,7 @@ use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_ma
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::csd_frame::FrameClick;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::compositor::SurfaceData;
use sctk::globals::GlobalData;
@@ -41,7 +44,21 @@ impl PointerHandler for WinitState {
events: &[PointerEvent],
) {
let seat = pointer.winit_data().seat();
let seat_state = self.seats.get(&seat.id()).unwrap();
let seat_state = match self.seats.get(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received pointer event without seat");
return;
},
};
let themed_pointer = match seat_state.pointer.as_ref() {
Some(pointer) => pointer,
None => {
warn!("Received pointer event without pointer");
return;
},
};
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
@@ -78,9 +95,7 @@ impl PointerHandler for WinitState {
event.position.0,
event.position.1,
) {
if let Some(pointer) = seat_state.pointer.as_ref() {
let _ = pointer.set_cursor(connection, icon);
}
let _ = themed_pointer.set_cursor(connection, icon);
}
},
PointerEventKind::Leave { .. } if parent_surface != surface => {
@@ -113,9 +128,7 @@ impl PointerHandler for WinitState {
self.events_sink
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_entered(pointer);
}
window.pointer_entered(Arc::downgrade(themed_pointer));
// Set the currently focused surface.
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
@@ -126,9 +139,7 @@ impl PointerHandler for WinitState {
);
},
PointerEventKind::Leave { .. } => {
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_left(pointer);
}
window.pointer_left(Arc::downgrade(themed_pointer));
// Remove the active surface.
pointer.winit_data().inner.lock().unwrap().surface = None;
@@ -183,15 +194,15 @@ impl PointerHandler for WinitState {
pointer_data.phase = phase;
// Mice events have both pixel and discrete delta's at the same time. So prefer
// the descrite values if they are present.
// the discrete values if they are present.
let delta = if has_discrete_scroll {
// XXX Wayland sign convention is the inverse of winit.
// NOTE: Wayland sign convention is the inverse of winit.
MouseScrollDelta::LineDelta(
(-horizontal.discrete) as f32,
(-vertical.discrete) as f32,
)
} else {
// XXX Wayland sign convention is the inverse of winit.
// NOTE: Wayland sign convention is the inverse of winit.
MouseScrollDelta::PixelDelta(
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
.to_physical(scale_factor),
@@ -215,13 +226,17 @@ pub struct WinitPointerData {
/// The data required by the sctk.
sctk_data: PointerData,
/// Viewport for fractional cursor.
viewport: Option<WpViewport>,
}
impl WinitPointerData {
pub fn new(seat: WlSeat) -> Self {
pub fn new(seat: WlSeat, viewport: Option<WpViewport>) -> Self {
Self {
inner: Mutex::new(WinitPointerDataInner::default()),
sctk_data: PointerData::new(seat),
viewport,
}
}
@@ -302,6 +317,18 @@ impl WinitPointerData {
locked_pointer.set_cursor_position_hint(surface_x, surface_y);
}
}
pub fn viewport(&self) -> Option<&WpViewport> {
self.viewport.as_ref()
}
}
impl Drop for WinitPointerData {
fn drop(&mut self) {
if let Some(viewport) = self.viewport.take() {
viewport.destroy();
}
}
}
impl PointerDataExt for WinitPointerData {

View File

@@ -121,11 +121,15 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
None => return,
};
// Clear preedit at the start of `Done`.
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
// Clear preedit, unless all we'll be doing next is sending a new preedit.
if text_input_data.pending_commit.is_some()
|| text_input_data.pending_preedit.is_none()
{
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
}
// Send `Commit`.
if let Some(text) = text_input_data.pending_commit.take() {

View File

@@ -1,5 +1,7 @@
//! Touch handling.
use tracing::warn;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
@@ -31,11 +33,16 @@ impl TouchHandler for WinitState {
None => return,
};
let location = LogicalPosition::<f64>::from(position);
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::down without seat");
return;
},
};
// Update the state of the point.
let location = LogicalPosition::<f64>::from(position);
seat_state.touch_map.insert(id, TouchPoint { surface, location });
self.events_sink.push_window_event(
@@ -61,7 +68,13 @@ impl TouchHandler for WinitState {
_: u32,
id: i32,
) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::up without seat");
return;
},
};
// Remove the touch point.
let touch_point = match seat_state.touch_map.remove(&id) {
@@ -98,7 +111,13 @@ impl TouchHandler for WinitState {
id: i32,
position: (f64, f64),
) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::motion without seat");
return;
},
};
// Remove the touch point.
let touch_point = match seat_state.touch_map.get_mut(&id) {
@@ -129,7 +148,13 @@ impl TouchHandler for WinitState {
}
fn cancel(&mut self, _: &Connection, _: &QueueHandle<Self>, touch: &WlTouch) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::cancel without seat");
return;
},
};
for (id, touch_point) in seat_state.touch_map.drain() {
let window_id = wayland::make_wid(&touch_point.surface);

View File

@@ -115,9 +115,6 @@ pub struct WinitState {
/// Whether we have dispatched events to the user thus we want to
/// send `AboutToWait` and normally wakeup the user.
pub dispatched_events: bool,
/// Whether the user initiated a wake up.
pub proxy_wake_up: bool,
}
impl WinitState {
@@ -195,7 +192,6 @@ impl WinitState {
loop_handle,
// Make it true by default.
dispatched_events: true,
proxy_wake_up: false,
})
}
@@ -343,12 +339,30 @@ impl CompositorHandler for WinitState {
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wayland_client::protocol::wl_surface::WlSurface,
_: &WlSurface,
_: wayland_client::protocol::wl_output::Transform,
) {
// TODO(kchibisov) we need to expose it somehow in winit.
}
fn surface_enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn surface_leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn scale_factor_changed(
&mut self,
_: &Connection,

View File

@@ -80,7 +80,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
state.events_sink.push_window_event(
crate::event::WindowEvent::ActivationTokenDone {
serial: *serial,
token: ActivationToken::_new(token),
token: ActivationToken::from_raw(token),
},
*window_id,
);

View File

@@ -1,5 +1,7 @@
//! The Wayland window.
use std::ffi::c_void;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
@@ -168,7 +170,7 @@ impl Window {
if let (Some(xdg_activation), Some(token)) =
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
{
xdg_activation.activate(token._token, &surface);
xdg_activation.activate(token.token, &surface);
}
// XXX Do initial commit.
@@ -223,6 +225,10 @@ impl Window {
window_events_sink,
})
}
pub(crate) fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast())
}
}
impl Window {

View File

@@ -31,7 +31,7 @@ use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as RootCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError};
use crate::platform_impl::wayland::logical_to_physical_rounded;
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
@@ -222,9 +222,9 @@ impl WindowState {
}
/// Apply closure on the given pointer.
fn apply_on_pointer<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self,
callback: F,
mut callback: F,
) {
self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
let data = pointer.pointer().winit_data();
@@ -726,17 +726,26 @@ impl WindowState {
}
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_pointer(|pointer, _| {
self.apply_on_pointer(|pointer, data| {
let surface = pointer.surface();
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
let scale = if let Some(viewport) = data.viewport() {
let scale = self.scale_factor();
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
viewport.set_destination(size.width, size.height);
scale
} else {
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
surface.set_buffer_scale(scale);
scale as f64
};
surface.set_buffer_scale(scale);
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
if surface.version() >= 4 {
surface.damage_buffer(0, 0, cursor.w, cursor.h);
} else {
surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
surface.damage(0, 0, size.width, size.height);
}
surface.commit();
@@ -746,12 +755,9 @@ impl WindowState {
.and_then(|data| data.pointer_data().latest_enter_serial())
.unwrap();
pointer.pointer().set_cursor(
serial,
Some(surface),
cursor.hotspot_x / scale,
cursor.hotspot_y / scale,
);
let hotspot =
PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale);
pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y);
});
}
@@ -827,34 +833,51 @@ impl WindowState {
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
};
// Replace the current mode.
let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
match old_mode {
CursorGrabMode::None => (),
let mut unset_old = false;
match self.cursor_grab_mode.current_grab_mode {
CursorGrabMode::None => unset_old = true,
CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
data.unconfine_pointer();
unset_old = true;
}),
CursorGrabMode::Locked => {
self.apply_on_pointer(|_, data| data.unlock_pointer());
self.apply_on_pointer(|_, data| {
data.unlock_pointer();
unset_old = true;
});
},
}
// In case we haven't unset the old mode, it means that we don't have a cursor above
// the window, thus just wait for it to re-appear.
if !unset_old {
return Ok(());
}
let mut set_mode = false;
let surface = self.window.wl_surface();
match mode {
CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer();
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
set_mode = true;
}),
CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer();
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
set_mode = true;
}),
CursorGrabMode::None => {
// Current lock/confine was already removed.
set_mode = true;
},
}
// Replace the current grab mode after we've ensure that it got updated.
if set_mode {
self.cursor_grab_mode.current_grab_mode = mode;
}
Ok(())
}

View File

@@ -165,14 +165,14 @@ fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
buffer: &'a mut Vec<u8>,
}
impl<'a> std::fmt::Write for Writer<'a> {
impl std::fmt::Write for Writer<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.extend_from_slice(s.as_bytes());
Ok(())
}
}
write!(Writer { buffer }, "{}", display).unwrap();
write!(Writer { buffer }, "{display}").unwrap();
}
#[cfg(test)]

View File

@@ -66,15 +66,18 @@ pub struct EventProcessor {
pub active_window: Option<xproto::Window>,
/// Latest modifiers we've sent for the user to trigger change in event.
pub modifiers: Cell<ModifiersState>,
pub xfiltered_modifiers: VecDeque<c_ulong>,
// Track modifiers based on keycodes. NOTE: that serials generally don't work for tracking
// since they are not unique and could be duplicated in case of sequence of key events is
// delivered at near the same time.
pub xfiltered_modifiers: VecDeque<u8>,
pub xmodmap: util::ModifierKeymap,
pub is_composing: bool,
}
impl EventProcessor {
pub(crate) fn process_event<F>(&mut self, xev: &mut XEvent, mut callback: F)
pub fn process_event<T: 'static, F>(&mut self, xev: &mut XEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
self.process_xevent(xev, &mut callback);
@@ -129,6 +132,7 @@ impl EventProcessor {
/// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
/// along with an extra copy of the KeyRelease events. This also prevents backspace and
/// arrow keys from being detected twice.
#[must_use]
fn filter_event(&mut self, xev: &mut XEvent) -> bool {
let wt = Self::window_target(&self.target);
unsafe {
@@ -139,26 +143,44 @@ impl EventProcessor {
}
}
fn process_xevent<F>(&mut self, xev: &mut XEvent, mut callback: F)
fn process_xevent<T: 'static, F>(&mut self, xev: &mut XEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let event_type = xev.get_type();
if self.filter_event(xev) {
if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
// If we have IME disabled, don't try to `filter_event`, since only IME can consume them
// and forward back. This is not desired for e.g. games since some IMEs may delay the input
// and game can toggle IME back when e.g. typing into some field where latency won't really
// matter.
let filtered = if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
let wt = Self::window_target(&self.target);
let ime = wt.ime.as_ref();
let window = self.active_window.map(|window| window as XWindow);
let forward_to_ime = ime
.and_then(|ime| window.map(|window| ime.borrow().is_ime_allowed(window)))
.unwrap_or(false);
let filtered = forward_to_ime && self.filter_event(xev);
if filtered {
let xev: &XKeyEvent = xev.as_ref();
if self.xmodmap.is_modifier(xev.keycode as u8) {
// Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen
// when the modifiers are consumed entirely or serials are altered.
//
// Both cases shouldn't happen in well behaving clients.
// when the modifiers are consumed entirely.
if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN {
self.xfiltered_modifiers.pop_back();
}
self.xfiltered_modifiers.push_front(xev.serial);
self.xfiltered_modifiers.push_front(xev.keycode as u8);
}
}
filtered
} else {
self.filter_event(xev)
};
// Don't process event if it was filtered.
if filtered {
return;
}
@@ -387,9 +409,9 @@ impl EventProcessor {
}
}
fn client_message<F>(&mut self, xev: &XClientMessageEvent, mut callback: F)
fn client_message<T: 'static, F>(&mut self, xev: &XClientMessageEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let atoms = wt.xconn.atoms();
@@ -552,9 +574,9 @@ impl EventProcessor {
}
}
fn selection_notify<F>(&mut self, xev: &XSelectionEvent, mut callback: F)
fn selection_notify<T: 'static, F>(&mut self, xev: &XSelectionEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let atoms = wt.xconn.atoms();
@@ -586,9 +608,9 @@ impl EventProcessor {
}
}
fn configure_notify<F>(&self, xev: &XConfigureEvent, mut callback: F)
fn configure_notify<T: 'static, F>(&self, xev: &XConfigureEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -781,9 +803,9 @@ impl EventProcessor {
});
}
fn map_notify<F>(&self, xev: &XMapEvent, mut callback: F)
fn map_notify<T: 'static, F>(&self, xev: &XMapEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
@@ -799,9 +821,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn destroy_notify<F>(&self, xev: &XDestroyWindowEvent, mut callback: F)
fn destroy_notify<T: 'static, F>(&self, xev: &XDestroyWindowEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -823,9 +845,9 @@ impl EventProcessor {
callback(&self.target, Event::WindowEvent { window_id, event: WindowEvent::Destroyed });
}
fn property_notify<F>(&mut self, xev: &XPropertyEvent, mut callback: F)
fn property_notify<T: 'static, F>(&mut self, xev: &XPropertyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let atoms = wt.x_connection().atoms();
@@ -838,9 +860,9 @@ impl EventProcessor {
}
}
fn visibility_notify<F>(&self, xev: &XVisibilityEvent, mut callback: F)
fn visibility_notify<T: 'static, F>(&self, xev: &XVisibilityEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let xwindow = xev.window as xproto::Window;
@@ -855,9 +877,9 @@ impl EventProcessor {
});
}
fn expose<F>(&self, xev: &XExposeEvent, mut callback: F)
fn expose<T: 'static, F>(&self, xev: &XExposeEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
// Multiple Expose events may be received for subareas of a window.
// We issue `RedrawRequested` only for the last event of such a series.
@@ -871,9 +893,13 @@ impl EventProcessor {
}
}
fn xinput_key_input<F>(&mut self, xev: &mut XKeyEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput_key_input<T: 'static, F>(
&mut self,
xev: &mut XKeyEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -925,7 +951,7 @@ impl EventProcessor {
// itself are out of sync due to XkbState being delivered before XKeyEvent, since it's
// being replayed by the XIM, thus we should replay ourselves.
let replay = if let Some(position) =
self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.serial)
self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.keycode as u8)
{
// We don't have to replay modifiers pressed before the current event if some events
// were not forwarded to us, since their state is irrelevant.
@@ -985,13 +1011,13 @@ impl EventProcessor {
}
}
fn send_synthic_modifier_from_core<F>(
fn send_synthic_modifier_from_core<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let keymap = match self.xkb_context.keymap_mut() {
Some(keymap) => keymap,
@@ -1018,9 +1044,13 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_button_input<F>(&self, event: &XIDeviceEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_button_input<T: 'static, F>(
&self,
event: &XIDeviceEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window_id = mkwid(event.event as xproto::Window);
@@ -1071,9 +1101,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_mouse_motion<F>(&self, event: &XIDeviceEvent, mut callback: F)
fn xinput2_mouse_motion<T: 'static, F>(&self, event: &XIDeviceEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1149,9 +1179,9 @@ impl EventProcessor {
}
}
fn xinput2_mouse_enter<F>(&self, event: &XIEnterEvent, mut callback: F)
fn xinput2_mouse_enter<T: 'static, F>(&self, event: &XIEnterEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1194,9 +1224,9 @@ impl EventProcessor {
}
}
fn xinput2_mouse_left<F>(&self, event: &XILeaveEvent, mut callback: F)
fn xinput2_mouse_left<T: 'static, F>(&self, event: &XILeaveEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window = event.event as xproto::Window;
@@ -1217,9 +1247,9 @@ impl EventProcessor {
}
}
fn xinput2_focused<F>(&mut self, xev: &XIFocusInEvent, mut callback: F)
fn xinput2_focused<T: 'static, F>(&mut self, xev: &XIFocusInEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window = xev.event as xproto::Window;
@@ -1276,9 +1306,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_unfocused<F>(&mut self, xev: &XIFocusOutEvent, mut callback: F)
fn xinput2_unfocused<T: 'static, F>(&mut self, xev: &XIFocusOutEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window = xev.event as xproto::Window;
@@ -1328,9 +1358,13 @@ impl EventProcessor {
}
}
fn xinput2_touch<F>(&mut self, xev: &XIDeviceEvent, phase: TouchPhase, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_touch<T: 'static, F>(
&mut self,
xev: &XIDeviceEvent,
phase: TouchPhase,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1370,9 +1404,13 @@ impl EventProcessor {
}
}
fn xinput2_raw_button_input<F>(&self, xev: &XIRawEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_raw_button_input<T: 'static, F>(
&self,
xev: &XIRawEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1388,9 +1426,9 @@ impl EventProcessor {
}
}
fn xinput2_raw_mouse_motion<F>(&self, xev: &XIRawEvent, mut callback: F)
fn xinput2_raw_mouse_motion<T: 'static, F>(&self, xev: &XIRawEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1448,9 +1486,13 @@ impl EventProcessor {
}
}
fn xinput2_raw_key_input<F>(&mut self, xev: &XIRawEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_raw_key_input<T: 'static, F>(
&mut self,
xev: &XIRawEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1470,9 +1512,9 @@ impl EventProcessor {
});
}
fn xinput2_hierarchy_changed<F>(&mut self, xev: &XIHierarchyEvent, mut callback: F)
fn xinput2_hierarchy_changed<T: 'static, F>(&mut self, xev: &XIHierarchyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1497,9 +1539,9 @@ impl EventProcessor {
}
}
fn xkb_event<F>(&mut self, xev: &XkbAnyEvent, mut callback: F)
fn xkb_event<T: 'static, F>(&mut self, xev: &XkbAnyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
match xev.xkb_type {
@@ -1576,14 +1618,14 @@ impl EventProcessor {
}
}
pub(crate) fn update_mods_from_xinput2_event<F>(
pub fn update_mods_from_xinput2_event<T: 'static, F>(
&mut self,
mods: &XIModifierState,
group: &XIModifierState,
force: bool,
mut callback: F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
if let Some(state) = self.xkb_context.state_mut() {
state.update_modifiers(
@@ -1607,9 +1649,12 @@ impl EventProcessor {
}
}
fn update_mods_from_query<F>(&mut self, window_id: crate::window::WindowId, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn update_mods_from_query<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1638,13 +1683,13 @@ impl EventProcessor {
self.send_modifiers(window_id, mods.into(), true, &mut callback)
}
pub(crate) fn update_mods_from_core_event<F>(
pub fn update_mods_from_core_event<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let xkb_mask = self.xkb_mod_mask_from_core(state);
let xkb_state = match self.xkb_context.state_mut() {
@@ -1717,7 +1762,7 @@ impl EventProcessor {
///
/// The event won't be sent when the `modifiers` match the previously `sent` modifiers value,
/// unless `force` is passed. The `force` should be passed when the active window changes.
fn send_modifiers<F: FnMut(&RootAEL, Event)>(
fn send_modifiers<T: 'static, F: FnMut(&RootAEL, Event<T>)>(
&self,
window_id: crate::window::WindowId,
modifiers: ModifiersState,
@@ -1735,14 +1780,14 @@ impl EventProcessor {
}
}
fn handle_pressed_keys<F>(
fn handle_pressed_keys<T: 'static, F>(
target: &RootAEL,
window_id: crate::window::WindowId,
state: ElementState,
xkb_context: &mut Context,
callback: &mut F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
@@ -1777,9 +1822,9 @@ impl EventProcessor {
}
}
fn process_dpi_change<F>(&self, callback: &mut F)
fn process_dpi_change<T: 'static, F>(&self, callback: &mut F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
wt.xconn.reload_database().expect("failed to reload Xft database");

View File

@@ -123,19 +123,15 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let is_allowed =
old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
// We can't use the style from the old context here, since it may change on reload, so
// pick style from the new XIM based on the old state.
let style = if is_allowed { new_im.preedit_style } else { new_im.none_style };
let new_context = {
let result = unsafe {
ImeContext::new(
xconn,
new_im.im,
style,
&new_im,
*window,
spot,
(*inner).event_sender.clone(),
is_allowed,
)
};
if result.is_err() {

View File

@@ -5,10 +5,9 @@ use std::{mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
use super::{ffi, util, XConnection, XError};
use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
/// IME creation error.
#[derive(Debug)]
@@ -184,7 +183,7 @@ struct ImeContextClientData {
pub struct ImeContext {
pub(crate) ic: ffi::XIC,
pub(crate) ic_spot: ffi::XPoint,
pub(crate) style: Style,
pub(crate) allowed: bool,
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
// from there we keep the pointer to automatically deallocate it.
_client_data: Box<ImeContextClientData>,
@@ -193,11 +192,11 @@ pub struct ImeContext {
impl ImeContext {
pub(crate) unsafe fn new(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: Style,
im: &InputMethod,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
event_sender: ImeEventSender,
allowed: bool,
) -> Result<Self, ImeContextCreationError> {
let client_data = Box::into_raw(Box::new(ImeContextClientData {
window,
@@ -206,20 +205,24 @@ impl ImeContext {
cursor_pos: 0,
}));
let style = if allowed { im.preedit_style } else { im.none_style };
let ic = match style as _ {
Style::Preedit(style) => unsafe {
ImeContext::create_preedit_ic(
xconn,
im,
im.im,
style,
window,
client_data as ffi::XPointer,
)
},
Style::Nothing(style) => unsafe {
ImeContext::create_nothing_ic(xconn, im, style, window)
ImeContext::create_nothing_ic(xconn, im.im, style, window)
},
Style::None(style) => unsafe {
ImeContext::create_none_ic(xconn, im.im, style, window)
},
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
}
.ok_or(ImeContextCreationError::Null)?;
@@ -228,7 +231,7 @@ impl ImeContext {
let mut context = ImeContext {
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
style,
allowed,
_client_data: unsafe { Box::from_raw(client_data) },
};
@@ -335,7 +338,7 @@ impl ImeContext {
}
pub fn is_allowed(&self) -> bool {
!matches!(self.style, Style::None(_))
self.allowed
}
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks

View File

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

View File

@@ -10,15 +10,13 @@ use std::sync::Arc;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use tracing::debug;
use super::{ffi, util, XConnection, XError};
use self::callbacks::*;
use self::context::ImeContext;
pub use self::context::ImeContextCreationError;
use self::inner::{close_im, ImeInner};
use self::input_method::{PotentialInputMethods, Style};
use self::input_method::PotentialInputMethods;
use super::{ffi, util, XConnection, XError};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -114,39 +112,26 @@ impl Ime {
pub fn create_context(
&mut self,
window: ffi::Window,
with_preedit: bool,
with_ime: bool,
) -> Result<bool, ImeContextCreationError> {
let context = if self.is_destroyed() {
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
None
} else {
let im = self.inner.im.as_ref().unwrap();
let style = if with_preedit { im.preedit_style } else { im.none_style };
let context = unsafe {
ImeContext::new(
&self.inner.xconn,
im.im,
style,
im,
window,
None,
self.inner.event_sender.clone(),
with_ime,
)?
};
// Check the state on the context, since it could fail to enable or disable preedit.
let event = if matches!(style, Style::None(_)) {
if with_preedit {
debug!("failed to create IME context with preedit support.")
}
ImeEvent::Disabled
} else {
if !with_preedit {
debug!("failed to create IME context without preedit support.")
}
ImeEvent::Enabled
};
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled };
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
Some(context)
@@ -226,6 +211,16 @@ impl Ime {
// Create new context supporting IME input.
let _ = self.create_context(window, allowed);
}
pub fn is_ime_allowed(&self, window: ffi::Window) -> bool {
if self.is_destroyed() {
false
} else if let Some(Some(context)) = self.inner.contexts.get(&window) {
context.is_allowed()
} else {
false
}
}
}
impl Drop for Ime {

View File

@@ -9,7 +9,7 @@ use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use std::{fmt, mem, ptr, slice, str};
use std::{fmt, ptr, slice, str};
use calloop::generic::Generic;
use calloop::ping::Ping;
@@ -25,10 +25,9 @@ use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::platform::{min_timeout, WindowId};
@@ -81,11 +80,12 @@ impl<T> Clone for WakeSender<T> {
}
impl<T> WakeSender<T> {
pub fn send(&self, t: T) {
let res = self.sender.send(t);
pub fn send(&self, t: T) -> Result<(), EventLoopClosed<T>> {
let res = self.sender.send(t).map_err(|e| EventLoopClosed(e.0));
if res.is_ok() {
self.waker.ping();
}
res
}
}
@@ -140,13 +140,15 @@ pub struct ActiveEventLoop {
device_events: Cell<DeviceEvents>,
}
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
loop_running: bool,
event_loop: Loop<'static, EventLoopState>,
waker: calloop::ping::Ping,
event_processor: EventProcessor,
redraw_receiver: PeekableReceiver<WindowId>,
user_receiver: PeekableReceiver<T>,
activation_receiver: PeekableReceiver<ActivationToken>,
event_loop_proxy: EventLoopProxy,
user_sender: Sender<T>,
/// The current state of the event loop.
state: EventLoopState,
@@ -157,13 +159,20 @@ type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial);
struct EventLoopState {
/// The latest readiness state for the x11 file descriptor
x11_readiness: Readiness,
/// User requested a wake up.
proxy_wake_up: bool,
}
impl EventLoop {
pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop {
pub struct EventLoopProxy<T: 'static> {
user_sender: WakeSender<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy { user_sender: self.user_sender.clone() }
}
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
let root = xconn.default_root().root;
let atoms = xconn.atoms();
@@ -267,16 +276,7 @@ impl EventLoop {
let (activation_token_sender, activation_token_channel) = mpsc::channel();
// Create a channel for sending user events.
let (user_waker, user_waker_source) =
calloop::ping::make_ping().expect("Failed to create user event loop waker.");
event_loop
.handle()
.insert_source(user_waker_source, move |_, _, state| {
// No extra handling is required, we just need to wake-up.
state.proxy_wake_up = true;
})
.expect("Failed to register the event loop waker source");
let event_loop_proxy = EventLoopProxy::new(user_waker);
let (user_sender, user_channel) = mpsc::channel();
let xkb_context =
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
@@ -357,32 +357,32 @@ impl EventLoop {
EventLoop {
loop_running: false,
event_loop,
waker,
event_processor,
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
event_loop_proxy,
state: EventLoopState { x11_readiness: Readiness::EMPTY, proxy_wake_up: false },
user_receiver: PeekableReceiver::from_recv(user_channel),
user_sender,
state: EventLoopState { x11_readiness: Readiness::EMPTY },
}
}
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop_proxy.clone()
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_sender: WakeSender { sender: self.user_sender.clone(), waker: self.waker.clone() },
}
}
pub(crate) fn window_target(&self) -> &RootAEL {
&self.event_processor.target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
let exit = loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -407,27 +407,26 @@ impl EventLoop {
exit
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootAEL),
{
if !self.loop_running {
self.loop_running = true;
// run the initial loop iteration
self.single_iteration(app, StartCause::Init);
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(self.window_target());
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
@@ -437,15 +436,14 @@ impl EventLoop {
fn has_pending(&mut self) -> bool {
self.event_processor.poll()
|| self.state.proxy_wake_up
|| self.user_receiver.has_incoming()
|| self.redraw_receiver.has_incoming()
}
pub fn poll_events_with_timeout<A: ApplicationHandler>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootAEL),
{
let start = Instant::now();
let has_pending = self.has_pending();
@@ -499,24 +497,28 @@ impl EventLoop {
// If we don't have any pending `_receiver`
if !self.has_pending()
&& !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll)
&& timeout.is_none()
{
return;
}
self.single_iteration(app, cause);
self.single_iteration(&mut callback, cause);
}
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
app.new_events(&self.event_processor.target, cause);
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootAEL),
{
callback(Event::NewEvents(cause), &self.event_processor.target);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
app.resumed(&self.event_processor.target)
callback(Event::Resumed, &self.event_processor.target);
}
// Process all pending events
self.drain_events(app);
self.drain_events(callback);
// Empty activation tokens.
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
@@ -526,12 +528,14 @@ impl EventLoop {
match token {
Some(Ok(token)) => {
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
let event = Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::from_raw(token),
},
};
app.window_event(&self.event_processor.target, window_id, event);
callback(event, &self.event_processor.target)
},
Some(Err(e)) => {
tracing::error!("Failed to get activation token: {}", e);
@@ -541,8 +545,10 @@ impl EventLoop {
}
// Empty the user event buffer
if mem::take(&mut self.state.proxy_wake_up) {
app.proxy_wake_up(&self.event_processor.target);
{
while let Ok(event) = self.user_receiver.try_recv() {
callback(Event::UserEvent(event), &self.event_processor.target);
}
}
// Empty the redraw requests
@@ -555,41 +561,37 @@ impl EventLoop {
for window_id in windows {
let window_id = crate::window::WindowId(window_id);
app.window_event(
callback(
Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested },
&self.event_processor.target,
window_id,
WindowEvent::RedrawRequested,
);
}
}
// This is always the last event we dispatch before poll again
app.about_to_wait(&self.event_processor.target);
{
callback(Event::AboutToWait, &self.event_processor.target);
}
}
fn drain_events<A: ApplicationHandler>(&mut self, app: &mut A) {
fn drain_events<F>(&mut self, callback: &mut F)
where
F: FnMut(Event<T>, &RootAEL),
{
let mut xev = MaybeUninit::uninit();
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |window_target, event: Event| {
self.event_processor.process_event(&mut xev, |window_target, event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid);
window_target.redraw_sender.send(wid).unwrap();
} else {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
callback(event, window_target);
}
});
}
@@ -616,13 +618,13 @@ impl EventLoop {
}
}
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
@@ -722,9 +724,9 @@ impl ActiveEventLoop {
}
}
impl EventLoopProxy {
pub fn wake_up(&self) {
self.ping.ping();
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| EventLoopClosed(e.0))
}
}
@@ -750,14 +752,14 @@ impl<'a> DeviceInfo<'a> {
}
}
impl<'a> Drop for DeviceInfo<'a> {
impl Drop for DeviceInfo<'_> {
fn drop(&mut self) {
assert!(!self.info.is_null());
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}
impl<'a> Deref for DeviceInfo<'a> {
impl Deref for DeviceInfo<'_> {
type Target = [ffi::XIDeviceInfo];
fn deref(&self) -> &Self::Target {
@@ -770,7 +772,7 @@ pub struct DeviceId(xinput::DeviceId);
impl DeviceId {
#[allow(unused)]
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId(0)
}
}
@@ -808,17 +810,6 @@ impl Drop for Window {
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
}
impl EventLoopProxy {
fn new(ping: Ping) -> Self {
Self { ping }
}
}
/// Generic sum error type for X11 errors.
#[derive(Debug)]
pub enum X11Error {
@@ -859,24 +850,24 @@ pub enum X11Error {
impl fmt::Display for X11Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X11Error::Xlib(e) => write!(f, "Xlib error: {}", e),
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e),
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
X11Error::Xlib(e) => write!(f, "Xlib error: {e}"),
X11Error::Connect(e) => write!(f, "X11 connection error: {e}"),
X11Error::Connection(e) => write!(f, "X11 connection error: {e}"),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {e}"),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {e}"),
X11Error::X11(e) => write!(f, "X11 error: {e:?}"),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {s}"),
X11Error::InvalidActivationToken(s) => write!(
f,
"Invalid activation token: {}",
std::str::from_utf8(s).unwrap_or("<invalid utf8>")
),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {s}"),
X11Error::NoSuchVisual(visualid) => {
write!(f, "Could not find a matching X11 visual for ID `{:x}`", visualid)
write!(f, "Could not find a matching X11 visual for ID `{visualid:x}`")
},
X11Error::XsettingsParse(err) => {
write!(f, "Failed to parse xsettings: {:?}", err)
write!(f, "Failed to parse xsettings: {err:?}")
},
}
}
@@ -967,7 +958,7 @@ trait CookieResultExt {
fn expect_then_ignore_error(self, msg: &str);
}
impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
impl<E: fmt::Debug> CookieResultExt for Result<VoidCookie<'_>, E> {
fn expect_then_ignore_error(self, msg: &str) {
self.expect(msg).ignore_error()
}

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ where
}
impl XConnection {
// This is impoartant, so pay attention!
// This is important, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.
// This buffer contains the requests you make, and is flushed under various circumstances:
// 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed"

View File

@@ -79,7 +79,7 @@ impl XConnection {
.iter()
// XRROutputInfo contains an array of mode ids that correspond to
// modes in the array in XRRScreenResources
.filter(|x| output_modes.iter().any(|id| x.id == *id))
.filter(|x| output_modes.contains(&x.id))
.map(|mode| {
VideoModeHandle {
size: (mode.width.into(), mode.height.into()),

View File

@@ -144,12 +144,26 @@ impl UnownedWindow {
) -> Result<UnownedWindow, RootOsError> {
let xconn = &event_loop.xconn;
let atoms = xconn.atoms();
let screen_id = match window_attrs.platform_specific.x11.screen_id {
Some(id) => id,
None => xconn.default_screen_index() as c_int,
};
let screen = {
let screen_id_usize = usize::try_from(screen_id)
.map_err(|_| os_error!(OsError::Misc("screen id must be non-negative")))?;
xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!(
OsError::Misc("requested screen id not present in server's response")
))?
};
#[cfg(feature = "rwh_06")]
let root = match window_attrs.parent_window.as_ref().map(|handle| handle.0) {
Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
None => event_loop.root,
None => screen.root,
};
#[cfg(not(feature = "rwh_06"))]
let root = event_loop.root;
@@ -207,18 +221,10 @@ impl UnownedWindow {
dimensions
};
let screen_id = match window_attrs.platform_specific.x11.screen_id {
Some(id) => id,
None => xconn.default_screen_index() as c_int,
};
// An iterator over all of the visuals combined with their depths.
let mut all_visuals = xconn
.xcb_connection()
.setup()
.roots
// An iterator over the visuals matching screen id combined with their depths.
let mut all_visuals = screen
.allowed_depths
.iter()
.flat_map(|root| &root.allowed_depths)
.flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth)));
// creating
@@ -484,6 +490,20 @@ impl UnownedWindow {
);
leap!(result).ignore_error();
// Select XInput2 events
let mask = xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS
| xinput::XIEventMask::BUTTON_RELEASE
| xinput::XIEventMask::ENTER
| xinput::XIEventMask::LEAVE
| xinput::XIEventMask::FOCUS_IN
| xinput::XIEventMask::FOCUS_OUT
| xinput::XIEventMask::TOUCH_BEGIN
| xinput::XIEventMask::TOUCH_UPDATE
| xinput::XIEventMask::TOUCH_END;
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
// Set visibility (map window)
if window_attrs.visible {
leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error();
@@ -507,20 +527,6 @@ impl UnownedWindow {
}
}
// Select XInput2 events
let mask = xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS
| xinput::XIEventMask::BUTTON_RELEASE
| xinput::XIEventMask::ENTER
| xinput::XIEventMask::LEAVE
| xinput::XIEventMask::FOCUS_IN
| xinput::XIEventMask::FOCUS_OUT
| xinput::XIEventMask::TOUCH_BEGIN
| xinput::XIEventMask::TOUCH_UPDATE
| xinput::XIEventMask::TOUCH_END;
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
// Try to create input context for the window.
if let Some(ime) = event_loop.ime.as_ref() {
let result = ime.borrow_mut().create_context(window.xwindow as ffi::Window, false);
@@ -553,7 +559,7 @@ impl UnownedWindow {
// Remove the startup notification if we have one.
if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() {
leap!(xconn.remove_activation_token(xwindow, &startup._token));
leap!(xconn.remove_activation_token(xwindow, &startup.token));
}
// We never want to give the user a broken window, since by then, it's too late to handle.
@@ -876,11 +882,11 @@ impl UnownedWindow {
/// Refresh the API for the given monitor.
#[inline]
pub(super) fn refresh_dpi_for_monitor(
pub(super) fn refresh_dpi_for_monitor<T: 'static>(
&self,
new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>,
mut callback: impl FnMut(Event),
mut callback: impl FnMut(Event<T>),
) {
// Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone();
@@ -970,8 +976,8 @@ impl UnownedWindow {
let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
match state {
Ok(atoms) => {
let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom);
let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom);
let horz_maximized = atoms.contains(&horz_atom);
let vert_maximized = atoms.contains(&vert_atom);
horz_maximized && vert_maximized
},
_ => false,
@@ -1486,6 +1492,11 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
// We don't support the locked cursor yet, so ignore it early on.
if mode == CursorGrabMode::Locked {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
if mode == *grabbed_lock {
return Ok(());
@@ -1497,40 +1508,40 @@ impl UnownedWindow {
.xcb_connection()
.ungrab_pointer(x11rb::CURRENT_TIME)
.expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`");
*grabbed_lock = CursorGrabMode::None;
let result = match mode {
CursorGrabMode::None => self.xconn.flush_requests().map_err(|err| {
ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into())))
}),
CursorGrabMode::Confined => {
let result = {
self.xconn
.xcb_connection()
.grab_pointer(
true as _,
self.xwindow,
xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::POINTER_MOTION
| xproto::EventMask::POINTER_MOTION_HINT
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::KEYMAP_STATE,
xproto::GrabMode::ASYNC,
xproto::GrabMode::ASYNC,
self.xwindow,
0u32,
x11rb::CURRENT_TIME,
)
.expect("Failed to call `grab_pointer`")
.reply()
.expect("Failed to receive reply from `grab_pointer`")
};
let result = self
.xconn
.xcb_connection()
.grab_pointer(
true as _,
self.xwindow,
xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::POINTER_MOTION
| xproto::EventMask::POINTER_MOTION_HINT
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::KEYMAP_STATE,
xproto::GrabMode::ASYNC,
xproto::GrabMode::ASYNC,
self.xwindow,
0u32,
x11rb::CURRENT_TIME,
)
.expect("Failed to call `grab_pointer`")
.reply()
.expect("Failed to receive reply from `grab_pointer`");
match result.status {
xproto::GrabStatus::SUCCESS => Ok(()),
@@ -1550,9 +1561,7 @@ impl UnownedWindow {
}
.map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err))))
},
CursorGrabMode::Locked => {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
},
CursorGrabMode::Locked => return Ok(()),
};
if result.is_ok() {
@@ -1805,7 +1814,9 @@ impl UnownedWindow {
#[inline]
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
let serial = AsyncRequestSerial::get();
self.activation_sender.send((self.id(), serial));
self.activation_sender
.send((self.id(), serial))
.expect("activation token channel should never be closed");
Ok(serial)
}
@@ -1816,7 +1827,7 @@ impl UnownedWindow {
#[inline]
pub fn request_redraw(&self) {
self.redraw_sender.send(WindowId(self.xwindow as _));
self.redraw_sender.send(WindowId(self.xwindow as _)).unwrap();
}
#[inline]

View File

@@ -145,7 +145,7 @@ impl XConnection {
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
// Fetch the _XSETTINGS_S[screen number] atom.
let xsettings_screen = xcb
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
.intern_atom(false, format!("_XSETTINGS_S{default_screen}").as_bytes())
.ok()?
.reply()
.ok()?

View File

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

View File

@@ -1,32 +1,29 @@
use std::cell::{Cell, RefCell};
use std::mem;
use std::rc::Weak;
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::time::Instant;
use objc2::rc::Retained;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSRunningApplication,
};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use super::event_handler::EventHandler;
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
use super::{menu, WindowId, DEVICE_ID};
use crate::event::{DeviceEvent, Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
use super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
use super::{menu, WindowId};
#[derive(Debug)]
pub(super) struct AppState {
activation_policy: NSApplicationActivationPolicy,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: RunLoop,
proxy_wake_up: Arc<AtomicBool>,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
@@ -79,14 +76,12 @@ declare_class!(
impl ApplicationDelegate {
pub(super) fn new(
mtm: MainThreadMarker,
activation_policy: NSApplicationActivationPolicy,
proxy_wake_up: Arc<AtomicBool>,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(AppState {
activation_policy,
proxy_wake_up,
default_menu,
activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm),
@@ -118,7 +113,24 @@ impl ApplicationDelegate {
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(self.ivars().activation_policy);
// If no activation policy is explicitly provided, do not set it at all
// to allow the package manifest to define behavior via LSUIElement.
if let Some(activation_policy) = self.ivars().activation_policy {
app.setActivationPolicy(activation_policy);
} else {
// If no activation policy is explicitly provided, and the application
// is bundled, do not set the activation policy at all, to allow the
// package manifest to define the behavior via LSUIElement.
//
// See:
// - https://github.com/rust-windowing/winit/issues/261
// - https://github.com/rust-windowing/winit/issues/3958
let is_bundled =
unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() };
if !is_bundled {
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
}
}
window_activation_hack(&app);
#[allow(deprecated)]
@@ -153,7 +165,9 @@ impl ApplicationDelegate {
fn will_terminate(&self, _notification: &NSNotification) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
notify_windows_of_exit(&app);
self.internal_exit();
}
@@ -173,7 +187,7 @@ impl ApplicationDelegate {
/// of the given closure.
pub fn set_event_handler<R>(
&self,
handler: &mut dyn ApplicationHandler,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop),
closure: impl FnOnce() -> R,
) -> R {
self.ivars().event_handler.set(handler, closure)
@@ -207,9 +221,7 @@ impl ApplicationDelegate {
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(&self) {
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.handle_event(Event::LoopExiting);
self.set_is_running(false);
self.set_stop_on_redraw(false);
@@ -250,13 +262,26 @@ impl ApplicationDelegate {
self.ivars().control_flow.get()
}
pub fn maybe_queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.maybe_queue_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
}
pub fn handle_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
}
pub fn maybe_queue_device_event(&self, event: DeviceEvent) {
self.maybe_queue_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
}
pub fn handle_redraw(&self, window_id: WindowId) {
let mtm = MainThreadMarker::from(self);
// Redraw request might come out of order from the OS.
// -> Don't go back into the event handler when our callstack originates from there
if !self.ivars().event_handler.in_use() {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
});
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
@@ -278,10 +303,7 @@ impl ApplicationDelegate {
}
#[track_caller]
pub fn maybe_queue_with_handler(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop) + 'static,
) {
fn maybe_queue_event(&self, event: Event<HandlePendingUserEvents>) {
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
// result in an event being queued, and applied at a later point.
//
@@ -289,31 +311,25 @@ impl ApplicationDelegate {
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
// currently handling another event, and if we are, we queue the event instead.
if !self.ivars().event_handler.in_use() {
self.with_handler(callback);
self.handle_event(event);
} else {
tracing::debug!("had to queue event since another is currently being handled");
tracing::debug!(?event, "had to queue event since another is currently being handled");
let this = self.retain();
self.ivars().run_loop.queue_closure(move || {
this.with_handler(callback);
});
self.ivars().run_loop.queue_closure(move || this.handle_event(event));
}
}
#[track_caller]
fn with_handler(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop),
) {
let event_loop = ActiveEventLoop::new_root(self.retain());
self.ivars().event_handler.handle(callback, &event_loop);
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
}
/// dispatch `NewEvents(Init)` + `Resumed`
pub fn dispatch_init_events(&self) {
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
self.handle_event(Event::NewEvents(StartCause::Init));
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
// applications don't themselves have a formal suspend/resume lifecycle.
self.with_handler(|app, event_loop| app.resumed(event_loop));
self.handle_event(Event::Resumed);
}
// Called by RunLoopObserver after finishing waiting for new events
@@ -323,7 +339,8 @@ impl ApplicationDelegate {
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !self.is_running() {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
return;
}
@@ -345,7 +362,7 @@ impl ApplicationDelegate {
},
};
self.with_handler(|app, event_loop| app.new_events(event_loop, cause));
self.handle_event(Event::NewEvents(cause));
}
// Called by RunLoopObserver before waiting for new events
@@ -355,27 +372,29 @@ impl ApplicationDelegate {
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !self.is_running() {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
return;
}
if self.ivars().proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}
self.handle_event(Event::UserEvent(HandlePendingUserEvents));
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
});
}
self.with_handler(|app, event_loop| {
app.about_to_wait(event_loop);
});
self.handle_event(Event::AboutToWait);
if self.exiting() {
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
notify_windows_of_exit(&app);
}
if self.ivars().stop_before_wait.get() {
@@ -393,6 +412,9 @@ impl ApplicationDelegate {
}
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
/// Returns the minimum `Option<Instant>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)

View File

@@ -4,7 +4,7 @@ use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{msg_send_id, sel, ClassType};
use objc2::{msg_send, msg_send_id, sel, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
@@ -66,7 +66,7 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class();
if cls.responds_to(sel) {
if msg_send![cls, respondsToSelector: sel] {
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor)
} else {

View File

@@ -7,12 +7,12 @@ use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_foundation::{run_on_main, NSPoint};
use smol_str::SmolStr;
use super::ffi;
use crate::event::{ElementState, KeyEvent, Modifiers};
use crate::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey,
};
use crate::platform_impl::platform::ffi;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {
@@ -92,17 +92,12 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
/// Create `KeyEvent` for the given `NSEvent`.
///
/// This function shouldn't be called when the IME input is in process.
pub(crate) fn create_key_event(
ns_event: &NSEvent,
is_press: bool,
is_repeat: bool,
key_override: Option<PhysicalKey>,
) -> KeyEvent {
pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released };
let scancode = unsafe { ns_event.keyCode() };
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32));
let mut physical_key = scancode_to_physicalkey(scancode as u32);
// NOTE: The logical key should heed both SHIFT and ALT if possible.
// For instance:
@@ -111,20 +106,15 @@ pub(crate) fn create_key_event(
// * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best.
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() {
None
} else {
let characters =
unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
if characters.is_empty() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
};
let key_from_code = code_to_key(physical_key, scancode);

View File

@@ -1,31 +1,32 @@
use std::cell::RefCell;
use std::{fmt, mem};
use crate::application::ApplicationHandler;
use super::app_state::HandlePendingUserEvents;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
#[derive(Default)]
struct EventHandlerData {
#[allow(clippy::type_complexity)]
handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
}
impl fmt::Debug for EventHandlerData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventHandlerData").finish_non_exhaustive()
}
}
#[derive(Debug)]
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop (None).
/// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
}
impl fmt::Debug for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = match self.inner.try_borrow().as_deref() {
Ok(Some(_)) => "<available>",
Ok(None) => "<not set>",
Err(_) => "<in use>",
};
f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
}
inner: RefCell<Option<EventHandlerData>>,
}
impl EventHandler {
pub(crate) fn new() -> Self {
pub(crate) const fn new() -> Self {
Self { inner: RefCell::new(None) }
}
@@ -36,7 +37,7 @@ impl EventHandler {
/// from within the closure.
pub(crate) fn set<'handler, R>(
&self,
app: &'handler mut dyn ApplicationHandler,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler,
closure: impl FnOnce() -> R,
) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can
@@ -47,9 +48,9 @@ impl EventHandler {
// extended beyond `'handler`.
let handler = unsafe {
mem::transmute::<
&'handler mut dyn ApplicationHandler,
&'static mut dyn ApplicationHandler,
>(app)
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
>(Box::new(handler))
};
match self.inner.try_borrow_mut().as_deref_mut() {
@@ -57,7 +58,7 @@ impl EventHandler {
unreachable!("tried to set handler while another was already set");
},
Ok(data @ None) => {
*data = Some(handler);
*data = Some(EventHandlerData { handler });
},
Err(_) => {
unreachable!("tried to set handler that is currently in use");
@@ -104,20 +105,24 @@ impl EventHandler {
self.inner.try_borrow().is_err()
}
pub(crate) fn handle(
pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub(crate) fn handle_event(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop),
event: Event<HandlePendingUserEvents>,
event_loop: &RootActiveEventLoop,
) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(user_app)) => {
Ok(Some(EventHandlerData { handler })) => {
// It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is
// still in use.
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
callback(*user_app, event_loop);
(handler)(event, event_loop);
},
Ok(None) => {
// `NSApplication`, our app delegate and this handler are all

View File

@@ -6,33 +6,34 @@ use std::os::raw::c_void;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopDefaultMode, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::ProtocolObject;
use objc2::{msg_send_id, ClassType};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use super::app::WinitApplication;
use super::app_state::ApplicationDelegate;
use super::cursor::CustomCursor;
use super::app::override_send_event;
use super::app_state::{ApplicationDelegate, HandlePendingUserEvents};
use super::event::dummy_event;
use super::monitor::{self, MonitorHandle};
use super::observer::setup_control_flow_observers;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents};
use crate::event::Event;
use crate::event_loop::{
ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource};
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
@@ -105,6 +106,17 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -154,7 +166,21 @@ impl ActiveEventLoop {
}
}
pub struct EventLoop {
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootWindowTarget),
receiver: Rc<mpsc::Receiver<T>>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
},
}
}
pub struct EventLoop<T: 'static> {
/// Store a reference to the application for convenience.
///
/// We intentionally don't store `WinitApplication` since we want to have
@@ -166,7 +192,9 @@ pub struct EventLoop {
/// keep it around here as well.
delegate: Retained<ApplicationDelegate>,
proxy_wake_up: Arc<AtomicBool>,
// Event sender and receiver, used for EventLoopProxy.
sender: mpsc::Sender<T>,
receiver: Rc<mpsc::Receiver<T>>,
window_target: RootWindowTarget,
panic_info: Rc<PanicInfo>,
@@ -174,50 +202,36 @@ pub struct EventLoop {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) activation_policy: ActivationPolicy,
pub(crate) activation_policy: Option<ActivationPolicy>,
pub(crate) default_menu: bool,
pub(crate) activate_ignoring_other_apps: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self {
activation_policy: Default::default(), // Regular
default_menu: true,
activate_ignoring_other_apps: true,
}
Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
}
}
impl EventLoop {
impl<T> EventLoop<T> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!");
let app: Retained<NSApplication> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
if !app.is_kind_of::<WinitApplication>() {
panic!(
"`winit` requires control over the principal class. You must create the event \
loop before other parts of your application initialize NSApplication"
);
}
// Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm);
let activation_policy = match attributes.activation_policy {
ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
};
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let delegate = ApplicationDelegate::new(
mtm,
activation_policy,
proxy_wake_up.clone(),
attributes.default_menu,
attributes.activate_ignoring_other_apps,
);
@@ -226,17 +240,22 @@ impl EventLoop {
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
});
// Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app);
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop {
app,
delegate: delegate.clone(),
sender,
receiver: Rc::new(receiver),
window_target: RootWindowTarget {
p: ActiveEventLoop { delegate, mtm },
_marker: PhantomData,
},
proxy_wake_up,
panic_info,
})
}
@@ -245,19 +264,24 @@ impl EventLoop {
&self.window_target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
self.run_on_demand(handler)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups.
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
self.delegate.set_event_handler(app, || {
pub fn run_on_demand<F>(&mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
// clear / normalize pump_events state
self.delegate.set_wait_timeout(None);
@@ -290,12 +314,13 @@ impl EventLoop {
Ok(())
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
self.delegate.set_event_handler(app, || {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, handler: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched.
@@ -359,8 +384,8 @@ impl EventLoop {
})
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
}
@@ -392,6 +417,22 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
});
}
/// Tell all windows to close.
///
/// This will synchronously trigger `WindowEvent::Destroyed` within
/// `windowWillClose:`, giving the application one last chance to handle
/// those events. It doesn't matter if the user also ends up closing the
/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
/// stays closed.
///
/// This ensures that no windows linger on after the event loop has exited,
/// see <https://github.com/rust-windowing/winit/issues/4135>.
pub(super) fn notify_windows_of_exit(app: &NSApplication) {
for window in app.windows() {
window.close();
}
}
/// Catches panics that happen inside `f` and when a panic
/// happens, stops the `sharedApplication`
#[inline]
@@ -418,15 +459,15 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
}
}
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
pub struct EventLoopProxy<T> {
sender: mpsc::Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
impl Drop for EventLoopProxy {
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
@@ -434,14 +475,14 @@ impl Drop for EventLoopProxy {
}
}
impl Clone for EventLoopProxy {
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy::new(self.proxy_wake_up.clone())
EventLoopProxy::new(self.sender.clone())
}
}
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> Self {
impl<T> EventLoopProxy<T> {
fn new(sender: mpsc::Sender<T>) -> Self {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -462,20 +503,21 @@ impl EventLoopProxy {
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { proxy_wake_up, source }
EventLoopProxy { sender, source }
}
}
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}

View File

@@ -68,6 +68,8 @@ pub type CGDisplayModeRef = *mut c_void;
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGDisplayGetDisplayIDFromUUID(uuid: CFUUIDRef) -> CGDirectDisplayID;
}
#[link(name = "CoreGraphics", kind = "framework")]

View File

@@ -37,7 +37,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}

View File

@@ -6,6 +6,7 @@ use std::fmt;
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
use core_foundation::base::{CFRelease, TCFType};
use core_foundation::string::CFString;
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUID};
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
@@ -13,6 +14,7 @@ use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2_app_kit::NSScreen;
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
use tracing::warn;
use super::ffi;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
@@ -97,18 +99,71 @@ impl VideoModeHandle {
}
}
/// `CGDirectDisplayID` is documented as:
/// > a framebuffer, a color correction (gamma) table, and possibly an attached monitor.
///
/// That is, it doesn't actually represent the monitor itself. Instead, we use the UUID of the
/// monitor, as retrieved from `CGDisplayCreateUUIDFromDisplayID` (this makes the monitor ID stable,
/// even across reboots and video mode changes).
///
/// NOTE: I'd be perfectly valid to store `[u8; 16]` in here instead, we only store `CFUUID` to
/// avoid having to re-create it when we want to fetch the display ID.
#[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID);
pub struct MonitorHandle(CFUUID);
// SAFETY: CFUUID is immutable.
// FIXME(madsmtm): Upstream this into `objc2-core-foundation`.
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
type MonitorUuid = [u8; 16];
impl MonitorHandle {
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
fn uuid(&self) -> MonitorUuid {
let uuid = unsafe { CFUUIDGetUUIDBytes(self.0.as_concrete_TypeRef()) };
MonitorUuid::from([
uuid.byte0,
uuid.byte1,
uuid.byte2,
uuid.byte3,
uuid.byte4,
uuid.byte5,
uuid.byte6,
uuid.byte7,
uuid.byte8,
uuid.byte9,
uuid.byte10,
uuid.byte11,
uuid.byte12,
uuid.byte13,
uuid.byte14,
uuid.byte15,
])
}
fn display_id(&self) -> CGDirectDisplayID {
unsafe { ffi::CGDisplayGetDisplayIDFromUUID(self.0.as_concrete_TypeRef()) }
}
#[track_caller]
pub(crate) fn new(display_id: CGDirectDisplayID) -> Option<Self> {
// kCGNullDirectDisplay
if display_id == 0 {
// `CGDisplayCreateUUIDFromDisplayID` checks kCGNullDirectDisplay internally.
warn!("constructing monitor from invalid display ID 0; falling back to main monitor");
}
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(display_id) };
if ptr.is_null() {
return None;
}
Some(Self(unsafe { CFUUID::wrap_under_create_rule(ptr) }))
}
}
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
self.uuid() == other.uuid()
}
}
@@ -122,18 +177,13 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
self.uuid().cmp(&other.uuid())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
self.uuid().hash(state);
}
}
@@ -141,7 +191,8 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
for display in displays {
monitors.push_back(MonitorHandle(display));
// Display ID just fetched from `CGGetActiveDisplayList`, should be fine to unwrap.
monitors.push_back(MonitorHandle::new(display).expect("invalid display ID"));
}
monitors
} else {
@@ -150,7 +201,8 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
}
pub fn primary_monitor() -> MonitorHandle {
MonitorHandle(CGDisplay::main().id)
// Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap.
MonitorHandle::new(CGDisplay::main().id).expect("invalid display ID")
}
impl fmt::Debug for MonitorHandle {
@@ -167,26 +219,20 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub fn new(id: CGDirectDisplayID) -> Self {
MonitorHandle(id)
}
// TODO: Be smarter about this:
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
pub fn name(&self) -> Option<String> {
let MonitorHandle(display_id) = *self;
let screen_num = CGDisplay::new(display_id).model_number();
let screen_num = CGDisplay::new(self.display_id()).model_number();
Some(format!("Monitor #{screen_num}"))
}
#[inline]
pub fn native_identifier(&self) -> u32 {
self.0
self.display_id()
}
pub fn size(&self) -> PhysicalSize<u32> {
let MonitorHandle(display_id) = *self;
let display = CGDisplay::new(display_id);
let display = CGDisplay::new(self.display_id());
let height = display.pixels_high();
let width = display.pixels_wide();
PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor())
@@ -213,14 +259,15 @@ impl MonitorHandle {
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
unsafe {
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
let current_display_mode =
NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()) as _);
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
if refresh_rate > 0.0 {
return Some((refresh_rate * 1000.0).round() as u32);
}
let mut display_link = std::ptr::null_mut();
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
if ffi::CVDisplayLinkCreateWithCGDisplay(self.display_id(), &mut display_link)
!= ffi::kCVReturnSuccess
{
return None;
@@ -243,27 +290,34 @@ impl MonitorHandle {
unsafe {
let modes = {
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null());
if array.is_null() {
// Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor
// during sleep/wake/cycling monitors. It tends to have null
// or 1 video mode only. See <https://github.com/bevyengine/bevy/issues/17827>.
warn!(monitor = ?self, "failed to get a list of display modes");
Vec::new()
} else {
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
}
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode);
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
(cg_refresh_rate_hertz * 1000) as u32
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0.0 {
(cg_refresh_rate_hertz * 1000.0).round() as u32
} else {
refresh_rate_millihertz
};
@@ -296,13 +350,17 @@ impl MonitorHandle {
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
let uuid = self.uuid();
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
let other_uuid = unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
};
uuid == other_uuid
if let Some(other) = MonitorHandle::new(other_native_id) {
uuid == other.uuid()
} else {
// Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(other_native_id, "comparing against screen with invalid display ID");
false
}
})
}
}

View File

@@ -13,8 +13,8 @@ use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopDefaultMode, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
@@ -129,7 +129,7 @@ impl RunLoop {
context,
)
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode) };
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
@@ -267,7 +267,7 @@ impl EventLoopWaker {
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopDefaultMode);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}

View File

@@ -2,8 +2,7 @@ use tracing::trace;
macro_rules! trace_scope {
($s:literal) => {
let _crate =
$crate::platform_impl::platform::appkit::util::TraceGuard::new(module_path!(), $s);
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
};
}

View File

@@ -20,18 +20,17 @@ use super::app_state::ApplicationDelegate;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey,
scancode_to_physicalkey, KeyEventExtra,
};
use super::window::WinitWindow;
use super::DEVICE_ID;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
struct CursorState {
@@ -400,7 +399,7 @@ declare_class!(
unsafe { &*string }.to_string()
};
let is_control = string.chars().next().map_or(false, |c| c.is_control());
let is_control = string.chars().next().is_some_and(|c| c.is_control());
// Commit only if we have marked text.
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
@@ -483,7 +482,7 @@ declare_class!(
};
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: key_event,
@@ -506,7 +505,7 @@ declare_class!(
) {
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: create_key_event(&event, false, false, None),
event: create_key_event(&event, false, false),
is_synthetic: false,
});
}
@@ -553,7 +552,7 @@ declare_class!(
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
let event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
@@ -687,9 +686,7 @@ declare_class!(
self.update_modifiers(event, false);
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
);
self.ivars().app_delegate.maybe_queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
@@ -833,10 +830,7 @@ impl WinitView {
}
fn queue_event(&self, event: WindowEvent) {
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
}
fn scale_factor(&self) -> f64 {
@@ -939,22 +933,36 @@ impl WinitView {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = scancode_to_physicalkey(scancode as u32);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
let logical_key = code_to_key(physical_key, scancode);
// Ignore processing of unknown modifiers because we can't determine whether
// it was pressed or release reliably.
let Some(event_modifier) = key_to_modifier(&key) else {
//
// Furthermore, sometimes normal keys are reported inside flagsChanged:, such as
// when holding Caps Lock while pressing another key, see:
// https://github.com/alacritty/alacritty/issues/8268
let Some(event_modifier) = key_to_modifier(&logical_key) else {
break 'send_event;
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let mut event = KeyEvent {
location: code_to_location(physical_key),
logical_key: logical_key.clone(),
physical_key,
repeat: false,
// We'll correct this later.
state: Pressed,
text: None,
platform_specific: KeyEventExtra {
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
};
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty());
let phys_mod =
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);

View File

@@ -74,7 +74,7 @@ impl Window {
pub struct WindowId(pub usize);
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}

View File

@@ -39,7 +39,7 @@ use crate::event::{InnerSizeWriter, WindowEvent};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
WindowAttributes, WindowButtons, WindowLevel,
};
#[derive(Clone, Debug)]
@@ -55,6 +55,7 @@ pub struct PlatformSpecificWindowAttributes {
pub accepts_first_mouse: bool,
pub tabbing_identifier: Option<String>,
pub option_as_alt: OptionAsAlt,
pub borderless_game: bool,
}
impl Default for PlatformSpecificWindowAttributes {
@@ -72,6 +73,7 @@ impl Default for PlatformSpecificWindowAttributes {
accepts_first_mouse: true,
tabbing_identifier: None,
option_as_alt: Default::default(),
borderless_game: false,
}
}
}
@@ -85,8 +87,8 @@ pub(crate) struct State {
// During `windowDidResize`, we use this to only send Moved if the position changed.
//
// This is expressed in native screen coordinates.
previous_position: Cell<Option<NSPoint>>,
// This is expressed in desktop coordinates, and flipped to match Winit's coordinate system.
previous_position: Cell<NSPoint>,
// Used to prevent redundant events.
previous_scale_factor: Cell<f64>,
@@ -120,6 +122,7 @@ pub(crate) struct State {
standard_frame: Cell<Option<NSRect>>,
is_simple_fullscreen: Cell<bool>,
saved_style: Cell<Option<NSWindowStyleMask>>,
is_borderless_game: Cell<bool>,
}
declare_class!(
@@ -711,7 +714,7 @@ impl WindowDelegate {
let delegate = mtm.alloc().set_ivars(State {
app_delegate: app_delegate.retain(),
window: window.retain(),
previous_position: Cell::new(None),
previous_position: Cell::new(flip_window_screen_coordinates(window.frame())),
previous_scale_factor: Cell::new(scale_factor),
resize_increments: Cell::new(resize_increments),
decorations: Cell::new(attrs.decorations),
@@ -725,6 +728,7 @@ impl WindowDelegate {
standard_frame: Cell::new(None),
is_simple_fullscreen: Cell::new(false),
saved_style: Cell::new(None),
is_borderless_game: Cell::new(attrs.platform_specific.borderless_game),
});
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
@@ -807,13 +811,11 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
}
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let app_delegate = &self.ivars().app_delegate;
let window = self.window();
let content_size = window.contentRectForFrameRect(window.frame()).size;
@@ -821,7 +823,7 @@ impl WindowDelegate {
let suggested_size = content_size.to_physical(scale_factor);
let new_inner_size = Arc::new(Mutex::new(suggested_size));
self.queue_event(WindowEvent::ScaleFactorChanged {
app_delegate.handle_window_event(window.id(), WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
});
@@ -833,17 +835,16 @@ impl WindowDelegate {
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
self.queue_event(WindowEvent::Resized(physical_size));
app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size));
}
fn emit_move_event(&self) {
let frame = self.window().frame();
if self.ivars().previous_position.get() == Some(frame.origin) {
let position = flip_window_screen_coordinates(self.window().frame());
if self.ivars().previous_position.get() == position {
return;
}
self.ivars().previous_position.set(Some(frame.origin));
self.ivars().previous_position.set(position);
let position = flip_window_screen_coordinates(frame);
let position =
LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor());
self.queue_event(WindowEvent::Moved(position));
@@ -1162,7 +1163,8 @@ impl WindowDelegate {
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
let mtm = MainThreadMarker::from(self);
let event = NSApplication::sharedApplication(mtm).currentEvent().unwrap();
let event =
NSApplication::sharedApplication(mtm).currentEvent().ok_or(ExternalError::Ignored)?;
self.window().performWindowDragWithEvent(&event);
Ok(())
}
@@ -1411,7 +1413,7 @@ impl WindowDelegate {
}
match (old_fullscreen, fullscreen) {
(None, Some(_)) => {
(None, Some(fullscreen)) => {
// `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`.
@@ -1421,6 +1423,17 @@ impl WindowDelegate {
self.set_style_mask(required);
self.ivars().saved_style.set(Some(curr_mask));
}
// In borderless games, we want to disable the dock and menu bar
// by setting the presentation options. We do this here rather than in
// `window:willUseFullScreenPresentationOptions` because for some reason
// the menu bar remains interactable despite being hidden.
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
app.setPresentationOptions(presentation_options);
}
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), None) => {
@@ -1428,13 +1441,7 @@ impl WindowDelegate {
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
};
restore_and_release_display(&video_mode.monitor());
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
@@ -1465,13 +1472,7 @@ impl WindowDelegate {
);
app.setPresentationOptions(presentation_options);
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
};
restore_and_release_display(&video_mode.monitor());
// Restore the normal window level following the Borderless fullscreen
// `CGShieldingWindowLevel() + 1` hack.
@@ -1591,7 +1592,14 @@ impl WindowDelegate {
// Allow directly accessing the current monitor internally without unwrapping.
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
let display_id = get_display_id(&*self.window().screen()?);
Some(MonitorHandle::new(display_id))
if let Some(monitor) = MonitorHandle::new(display_id) {
Some(monitor)
} else {
// NOTE: Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(display_id, "got screen with invalid display ID");
None
}
}
#[inline]
@@ -1615,7 +1623,7 @@ impl WindowDelegate {
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::AppKitHandle::empty();
window_handle.ns_window = self.window() as *const WinitWindow as *mut _;
window_handle.ns_view = Retained::as_ptr(&self.contentView().unwrap()) as *mut _;
window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _;
rwh_04::RawWindowHandle::AppKit(window_handle)
}
@@ -1659,14 +1667,18 @@ impl WindowDelegate {
}
pub fn theme(&self) -> Option<Theme> {
// Note: We could choose between returning the value of `effectiveAppearance` or
// `appearance`, depending on what the user is asking about:
// - "how should I render on this particular frame".
// - "what is the configuration for this window".
//
// We choose the latter for consistency with the `set_theme` call, though it might also be
// useful to expose the former.
Some(appearance_to_theme(unsafe { &*self.window().appearance()? }))
unsafe { self.window().appearance() }
.map(|appearance| appearance_to_theme(&appearance))
.or_else(|| {
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
})
}
pub fn set_theme(&self, theme: Option<Theme>) {
@@ -1691,6 +1703,21 @@ impl WindowDelegate {
}
}
fn restore_and_release_display(monitor: &MonitorHandle) {
let available_monitors = monitor::available_monitors();
if available_monitors.contains(monitor) {
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess);
};
} else {
warn!(
monitor = monitor.name(),
"Tried to restore exclusive fullscreen on a monitor that is no longer available"
);
}
}
impl WindowExtMacOS for WindowDelegate {
#[inline]
fn simple_fullscreen(&self) -> bool {
@@ -1726,9 +1753,13 @@ impl WindowExtMacOS for WindowDelegate {
self.ivars().is_simple_fullscreen.set(true);
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
let presentation_options =
let presentation_options = if self.is_borderless_game() {
NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar
} else {
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
};
app.setPresentationOptions(presentation_options);
// Hide the titlebar
@@ -1742,11 +1773,8 @@ impl WindowExtMacOS for WindowDelegate {
self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false);
self.toggle_style_mask(NSWindowStyleMask::Resizable, false);
self.window().setMovable(false);
true
} else {
let new_mask = self.saved_style();
self.set_style_mask(new_mask);
self.ivars().is_simple_fullscreen.set(false);
let save_presentation_opts = self.ivars().save_presentation_opts.get();
@@ -1758,9 +1786,10 @@ impl WindowExtMacOS for WindowDelegate {
self.window().setFrame_display(frame, true);
self.window().setMovable(true);
true
self.set_style_mask(new_mask);
}
true
}
#[inline]
@@ -1824,6 +1853,14 @@ impl WindowExtMacOS for WindowDelegate {
fn option_as_alt(&self) -> OptionAsAlt {
self.view().option_as_alt()
}
fn set_borderless_game(&self, borderless_game: bool) {
self.ivars().is_borderless_game.set(borderless_game);
}
fn is_borderless_game(&self) -> bool {
self.ivars().is_borderless_game.get()
}
}
const DEFAULT_STANDARD_FRAME: NSRect =
@@ -1834,7 +1871,7 @@ fn dark_appearance_name() -> &'static NSString {
ns_string!("NSAppearanceNameDarkAqua")
}
fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
unsafe { NSAppearanceNameAqua.copy() },
dark_appearance_name().copy(),

View File

@@ -3,10 +3,12 @@ use crate::window::Fullscreen as RootFullscreen;
#[cfg(android_platform)]
mod android;
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(ios_platform)]
mod ios;
#[cfg(any(x11_platform, wayland_platform))]
mod linux;
#[cfg(macos_platform)]
mod macos;
#[cfg(orbital_platform)]
mod orbital;
#[cfg(web_platform)]
@@ -15,17 +17,19 @@ mod web;
mod windows;
#[cfg(android_platform)]
use self::android as platform;
#[cfg(target_vendor = "apple")]
use self::apple as platform;
use android as platform;
#[cfg(ios_platform)]
use ios as platform;
#[cfg(any(x11_platform, wayland_platform))]
use self::linux as platform;
use linux as platform;
#[cfg(macos_platform)]
use macos as platform;
#[cfg(orbital_platform)]
use self::orbital as platform;
use orbital as platform;
#[cfg(web_platform)]
use self::web as platform;
use web as platform;
#[cfg(windows_platform)]
use self::windows as platform;
use windows as platform;
pub use self::platform::*;

View File

@@ -12,7 +12,6 @@ use orbclient::{
};
use smol_str::SmolStr;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::{self, Ime, Modifiers, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
@@ -21,7 +20,7 @@ use crate::keyboard::{
PhysicalKey,
};
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId,
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId,
};
use super::{
@@ -272,18 +271,16 @@ impl EventState {
}
}
pub struct EventLoop {
pub struct EventLoop<T> {
windows: Vec<(Arc<RedoxSocket>, EventState)>,
window_target: event_loop::ActiveEventLoop,
user_events_sender: mpsc::SyncSender<()>,
user_events_receiver: mpsc::Receiver<()>,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: mpsc::Receiver<T>,
}
impl EventLoop {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
// NOTE: Create a channel which can hold only one event to automatically _squash_ user
// events.
let (user_events_sender, user_events_receiver) = mpsc::sync_channel(1);
let (user_events_sender, user_events_receiver) = mpsc::channel();
let event_socket = Arc::new(
RedoxSocket::event()
@@ -325,13 +322,14 @@ impl EventLoop {
})
}
fn process_event<A: ApplicationHandler>(
fn process_event<F>(
window_id: WindowId,
event_option: EventOption,
event_state: &mut EventState,
window_target: &event_loop::ActiveEventLoop,
app: &mut A,
) {
mut event_handler: F,
) where
F: FnMut(event::Event<T>),
{
match event_option {
EventOption::Key(KeyEvent { character, scancode, pressed }) => {
// Convert scancode
@@ -373,128 +371,125 @@ impl EventLoop {
key_without_modifiers = logical_key.clone();
}
let window_id = RootWindowId(window_id);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
},
is_synthetic: false,
},
is_synthetic: false,
};
app.window_event(window_target, window_id, event);
});
// If the state of the modifiers has changed, send the event.
if modifiers_before != event_state.keyboard {
app.window_event(
window_target,
window_id,
event::WindowEvent::ModifiersChanged(event_state.modifiers()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
})
}
},
EventOption::TextInput(TextInputEvent { character }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
);
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Commit(character.into())),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
});
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Commit(character.into())),
});
},
EventOption::Mouse(MouseEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CursorMoved {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorMoved {
device_id: event::DeviceId(DeviceId),
position: (x, y).into(),
},
);
});
},
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
app.device_event(
window_target,
event::DeviceId(DeviceId),
event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
);
event_handler(event::Event::DeviceEvent {
device_id: event::DeviceId(DeviceId),
event: event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
});
},
EventOption::Button(ButtonEvent { left, middle, right }) => {
while let Some((button, state)) = event_state.mouse(left, middle, right) {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseInput {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseInput {
device_id: event::DeviceId(DeviceId),
state,
button,
},
);
});
}
},
EventOption::Scroll(ScrollEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseWheel {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseWheel {
device_id: event::DeviceId(DeviceId),
delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: event::TouchPhase::Moved,
},
);
});
},
EventOption::Quit(QuitEvent {}) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CloseRequested,
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CloseRequested,
});
},
EventOption::Focus(FocusEvent { focused }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Focused(focused),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Focused(focused),
});
},
EventOption::Move(MoveEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Moved((x, y).into()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((x, y).into()),
});
},
EventOption::Resize(ResizeEvent { width, height }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Resized((width, height).into()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((width, height).into()),
});
// Acknowledge resize after event loop.
event_state.resize_opt = Some((width, height));
},
// TODO: Screen, Clipboard, Drop
EventOption::Hover(HoverEvent { entered }) => {
let event = if entered {
event::WindowEvent::CursorEntered { device_id: event::DeviceId(DeviceId) }
if entered {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorEntered {
device_id: event::DeviceId(DeviceId),
},
});
} else {
event::WindowEvent::CursorLeft { device_id: event::DeviceId(DeviceId) }
};
app.window_event(window_target, RootWindowId(window_id), event);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorLeft {
device_id: event::DeviceId(DeviceId),
},
});
}
},
other => {
tracing::warn!("unhandled event: {:?}", other);
@@ -502,13 +497,22 @@ impl EventLoop {
}
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run<F>(mut self, mut event_handler_inner: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
let mut event_handler =
move |event: event::Event<T>, window_target: &event_loop::ActiveEventLoop| {
event_handler_inner(event, window_target);
};
let mut start_cause = StartCause::Init;
loop {
app.new_events(&self.window_target, start_cause);
event_handler(event::Event::NewEvents(start_cause), &self.window_target);
if start_cause == StartCause::Init {
app.resumed(&self.window_target);
event_handler(event::Event::Resumed, &self.window_target);
}
// Handle window creates.
@@ -524,15 +528,23 @@ impl EventLoop {
self.windows.push((window, EventState::default()));
let window_id = RootWindowId(window_id);
// Send resize event on create to indicate first size.
let event = event::WindowEvent::Resized((properties.w, properties.h).into());
app.window_event(&self.window_target, window_id, event);
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((properties.w, properties.h).into()),
},
&self.window_target,
);
// Send moved event on create to indicate first position.
let event = event::WindowEvent::Moved((properties.x, properties.y).into());
app.window_event(&self.window_target, window_id, event);
// Send resize event on create to indicate first position.
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((properties.x, properties.y).into()),
},
&self.window_target,
);
}
// Handle window destroys.
@@ -540,8 +552,14 @@ impl EventLoop {
let mut destroys = self.window_target.p.destroys.lock().unwrap();
destroys.pop_front()
} {
let window_id = RootWindowId(destroy_id);
app.window_event(&self.window_target, window_id, event::WindowEvent::Destroyed);
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(destroy_id),
event: event::WindowEvent::Destroyed,
},
&self.window_target,
);
self.windows.retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd);
}
@@ -568,8 +586,7 @@ impl EventLoop {
window_id,
orbital_event.to_option(),
event_state,
&self.window_target,
app,
|event| event_handler(event, &self.window_target),
);
}
@@ -596,8 +613,8 @@ impl EventLoop {
i += 1;
}
while self.user_events_receiver.try_recv().is_ok() {
app.proxy_wake_up(&self.window_target);
while let Ok(event) = self.user_events_receiver.try_recv() {
event_handler(event::Event::UserEvent(event), &self.window_target);
}
// To avoid deadlocks the redraws lock is not held during event processing.
@@ -605,14 +622,16 @@ impl EventLoop {
let mut redraws = self.window_target.p.redraws.lock().unwrap();
redraws.pop_front()
} {
app.window_event(
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::RedrawRequested,
},
&self.window_target,
RootWindowId(window_id),
event::WindowEvent::RedrawRequested,
);
}
app.about_to_wait(&self.window_target);
event_handler(event::Event::AboutToWait, &self.window_target);
if self.window_target.p.exiting() {
break;
@@ -676,7 +695,7 @@ impl EventLoop {
}
}
app.exiting(&self.window_target);
event_handler(event::Event::LoopExiting, &self.window_target);
Ok(())
}
@@ -685,7 +704,7 @@ impl EventLoop {
&self.window_target
}
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
wake_socket: self.window_target.p.wake_socket.clone(),
@@ -693,22 +712,24 @@ impl EventLoop {
}
}
pub struct EventLoopProxy {
user_events_sender: mpsc::SyncSender<()>,
pub struct EventLoopProxy<T: 'static> {
user_events_sender: mpsc::Sender<T>,
wake_socket: Arc<TimeSocket>,
}
impl EventLoopProxy {
pub fn wake_up(&self) {
// When we fail to send the event it means that we haven't woken up to read the previous
// event.
if self.user_events_sender.try_send(()).is_ok() {
self.wake_socket.wake().unwrap();
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?;
self.wake_socket.wake().unwrap();
Ok(())
}
}
impl Clone for EventLoopProxy {
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
user_events_sender: self.user_events_sender.clone(),
@@ -717,7 +738,7 @@ impl Clone for EventLoopProxy {
}
}
impl Unpin for EventLoopProxy {}
impl<T> Unpin for EventLoopProxy<T> {}
pub struct ActiveEventLoop {
control_flow: Cell<ControlFlow>,
@@ -754,6 +775,11 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

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

View File

@@ -420,7 +420,7 @@ impl Window {
window::ResizeDirection::West => "L",
};
self.window_socket
.write(format!("D,{}", arg).as_bytes())
.write(format!("D,{arg}").as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}

View File

@@ -1,7 +1,8 @@
use std::future;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, RecvError, SendError, TryRecvError};
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::task::Poll;
use super::AtomicWaker;
@@ -10,35 +11,54 @@ pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let (sender, receiver) = mpsc::channel();
let shared = Arc::new(Shared { closed: AtomicBool::new(false), waker: AtomicWaker::new() });
let sender = Sender { sender, shared: Arc::clone(&shared) };
let receiver = Receiver { receiver, shared };
let sender =
Sender(Arc::new(SenderInner { sender: Mutex::new(sender), shared: Arc::clone(&shared) }));
let receiver = Receiver { receiver: Rc::new(receiver), shared };
(sender, receiver)
}
pub struct Sender<T> {
sender: mpsc::Sender<T>,
pub struct Sender<T>(Arc<SenderInner<T>>);
struct SenderInner<T> {
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
// be accessed on the main thread, as it could block. Additionally we need
// to wrap `Sender` in an `Arc` to make it cloneable on the main thread without
// having to block.
sender: Mutex<mpsc::Sender<T>>,
shared: Arc<Shared>,
}
impl<T> Sender<T> {
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
self.sender.send(event)?;
self.shared.waker.wake();
self.0.sender.lock().unwrap().send(event)?;
self.0.shared.waker.wake();
Ok(())
}
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
impl<T> SenderInner<T> {
fn close(&self) {
self.shared.closed.store(true, Ordering::Relaxed);
self.shared.waker.wake();
}
}
impl<T> Clone for Sender<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T> Drop for SenderInner<T> {
fn drop(&mut self) {
self.close();
}
}
pub struct Receiver<T> {
receiver: mpsc::Receiver<T>,
receiver: Rc<mpsc::Receiver<T>>,
shared: Arc<Shared>,
}
@@ -75,6 +95,18 @@ impl<T> Receiver<T> {
}
}
impl<T> Clone for Receiver<T> {
fn clone(&self) -> Self {
Self { receiver: Rc::clone(&self.receiver), shared: Arc::clone(&self.shared) }
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.shared.closed.store(true, Ordering::Relaxed);
}
}
struct Shared {
closed: AtomicBool,
waker: AtomicWaker,

View File

@@ -1,10 +1,9 @@
use super::super::main_thread::MainThreadMarker;
use super::{channel, Receiver, Sender, Wrapper};
use std::cell::Ref;
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex};
pub struct Dispatcher<T: 'static>(Wrapper<T, Arc<Sender<Closure<T>>>, Closure<T>>);
pub struct Dispatcher<T: 'static>(Wrapper<T, Sender<Closure<T>>, Closure<T>>);
struct Closure<T>(Box<dyn FnOnce(&T) + Send>);
@@ -12,8 +11,6 @@ impl<T> Dispatcher<T> {
#[track_caller]
pub fn new(main_thread: MainThreadMarker, value: T) -> Option<(Self, DispatchRunner<T>)> {
let (sender, receiver) = channel::<Closure<T>>();
let sender = Arc::new(sender);
let receiver = Rc::new(receiver);
Wrapper::new(
main_thread,
@@ -24,7 +21,7 @@ impl<T> Dispatcher<T> {
closure(value.borrow().as_ref().unwrap())
},
{
let receiver = Rc::clone(&receiver);
let receiver = receiver.clone();
move |value| async move {
while let Ok(Closure(closure)) = receiver.next().await {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't
@@ -92,8 +89,8 @@ impl<T> Dispatcher<T> {
}
pub struct DispatchRunner<T: 'static> {
wrapper: Wrapper<T, Arc<Sender<Closure<T>>>, Closure<T>>,
receiver: Rc<Receiver<Closure<T>>>,
wrapper: Wrapper<T, Sender<Closure<T>>, Closure<T>>,
receiver: Receiver<Closure<T>>,
}
impl<T> DispatchRunner<T> {

View File

@@ -1,18 +1,19 @@
use std::future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::Poll;
use super::super::main_thread::MainThreadMarker;
use super::{AtomicWaker, Wrapper};
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, ()>);
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, ()>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
struct Handler<T> {
value: T,
handler: fn(&T, bool),
handler: fn(&T, NonZeroUsize, bool),
}
#[derive(Clone)]
@@ -20,9 +21,13 @@ struct Sender(Arc<Inner>);
impl<T> WakerSpawner<T> {
#[track_caller]
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, bool)) -> Option<Self> {
pub fn new(
main_thread: MainThreadMarker,
value: T,
handler: fn(&T, NonZeroUsize, bool),
) -> Option<Self> {
let inner = Arc::new(Inner {
awoken: AtomicBool::new(false),
counter: AtomicUsize::new(0),
waker: AtomicWaker::new(),
closed: AtomicBool::new(false),
});
@@ -34,43 +39,49 @@ impl<T> WakerSpawner<T> {
let wrapper = Wrapper::new(
main_thread,
handler,
|handler, _| {
|handler, count| {
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, true);
(handler.handler)(&handler.value, count, true);
},
{
let inner = Arc::clone(&inner);
move |handler| async move {
while future::poll_fn(|cx| {
if inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
inner.waker.register(cx.waker());
while let Some(count) = future::poll_fn(|cx| {
let count = inner.counter.swap(0, Ordering::Relaxed);
if inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(false);
match NonZeroUsize::new(count) {
Some(count) => Poll::Ready(Some(count)),
None => {
inner.waker.register(cx.waker());
let count = inner.counter.swap(0, Ordering::Relaxed);
match NonZeroUsize::new(count) {
Some(count) => Poll::Ready(Some(count)),
None => {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(None);
}
Poll::Pending
},
}
Poll::Pending
}
},
}
})
.await
{
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, false);
(handler.handler)(&handler.value, count, false);
}
}
},
sender,
|inner, _| {
inner.0.awoken.store(true, Ordering::Relaxed);
inner.0.counter.fetch_add(1, Ordering::Relaxed);
inner.0.waker.wake();
},
)?;
@@ -82,13 +93,13 @@ impl<T> WakerSpawner<T> {
Waker(self.0.clone())
}
pub fn take(&self) -> bool {
pub fn fetch(&self) -> usize {
debug_assert!(
MainThreadMarker::new().is_some(),
"this should only be called from the main thread"
);
self.0.with_sender_data(|inner| inner.0.awoken.swap(false, Ordering::Relaxed))
self.0.with_sender_data(|inner| inner.0.counter.swap(0, Ordering::Relaxed))
}
}
@@ -103,7 +114,7 @@ impl<T> Drop for WakerSpawner<T> {
impl<T> Waker<T> {
pub fn wake(&self) {
self.0.send(())
self.0.send(NonZeroUsize::MIN)
}
}
@@ -114,7 +125,7 @@ impl<T> Clone for Waker<T> {
}
struct Inner {
awoken: AtomicBool,
counter: AtomicUsize,
waker: AtomicWaker,
closed: AtomicBool,
}

View File

@@ -542,8 +542,8 @@ fn from_rgba(
//
// We call `createImageBitmap()` before spawning the future,
// to not have to clone the image buffer.
let mut options = ImageBitmapOptions::new();
options.premultiply_alpha(PremultiplyAlpha::None);
let options = ImageBitmapOptions::new();
options.set_premultiply_alpha(PremultiplyAlpha::None);
let bitmap = JsFuture::from(
window
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)

View File

@@ -2,7 +2,7 @@
pub struct DeviceId(pub i32);
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}

View File

@@ -1,6 +1,6 @@
use std::marker::PhantomData;
use std::sync::mpsc::{self, Receiver, Sender};
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
@@ -16,30 +16,48 @@ mod window_target;
pub(crate) use proxy::EventLoopProxy;
pub(crate) use window_target::{ActiveEventLoop, OwnedDisplayHandle};
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
elw: RootActiveEventLoop,
user_event_sender: Sender<T>,
user_event_receiver: Receiver<T>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
impl<T> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let (user_event_sender, user_event_receiver) = mpsc::channel();
let elw = RootActiveEventLoop { p: ActiveEventLoop::new(), _marker: PhantomData };
Ok(EventLoop { elw })
Ok(EventLoop { elw, user_event_sender, user_event_receiver })
}
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> ! {
pub fn run<F>(self, mut event_handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
let handler: Box<dyn FnMut(Event)> = Box::new(|event| handle_event(app, &target, event));
let handler: Box<dyn FnMut(Event<()>)> = Box::new(|event| {
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
});
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
// because this function will never return and all resources not cleaned up by the point we
// `throw` will leak, making this actually `'static`.
let handler = unsafe {
std::mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event) + 'static>>(handler)
std::mem::transmute::<Box<dyn FnMut(Event<()>)>, Box<dyn FnMut(Event<()>) + 'static>>(
handler,
)
};
self.elw.p.run(handler, false);
@@ -52,14 +70,31 @@ impl EventLoop {
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler + 'static>(self, mut app: A) {
pub fn spawn<F>(self, mut event_handler: F)
where
F: 'static + FnMut(Event<T>, &RootActiveEventLoop),
{
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
self.elw.p.run(Box::new(move |event| handle_event(&mut app, &target, event)), true);
self.elw.p.run(
Box::new(move |event| {
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
}),
true,
);
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.elw.p.waker())
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.elw.p.waker(), self.user_event_sender.clone())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
@@ -82,17 +117,3 @@ impl EventLoop {
self.elw.wait_until_strategy()
}
}
fn handle_event<A: ApplicationHandler>(app: &mut A, target: &RootActiveEventLoop, event: Event) {
match event {
Event::NewEvents(cause) => app.new_events(target, cause),
Event::WindowEvent { window_id, event } => app.window_event(target, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(target, device_id, event),
Event::UserWakeUp => app.proxy_wake_up(target),
Event::Suspended => app.suspended(target),
Event::Resumed => app.resumed(target),
Event::AboutToWait => app.about_to_wait(target),
Event::LoopExiting => app.exiting(target),
Event::MemoryWarning => app.memory_warning(target),
}
}

View File

@@ -1,19 +1,29 @@
use std::rc::Weak;
use std::sync::mpsc::{SendError, Sender};
use super::runner::Execution;
use crate::event_loop::EventLoopClosed;
use crate::platform_impl::platform::r#async::Waker;
#[derive(Clone)]
pub struct EventLoopProxy {
pub struct EventLoopProxy<T: 'static> {
runner: Waker<Weak<Execution>>,
sender: Sender<T>,
}
impl EventLoopProxy {
pub fn new(runner: Waker<Weak<Execution>>) -> Self {
Self { runner }
impl<T: 'static> EventLoopProxy<T> {
pub fn new(runner: Waker<Weak<Execution>>, sender: Sender<T>) -> Self {
Self { runner, sender }
}
pub fn wake_up(&self) {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|SendError(event)| EventLoopClosed(event))?;
self.runner.wake();
Ok(())
}
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { runner: self.runner.clone(), sender: self.sender.clone() }
}
}

View File

@@ -1,3 +1,15 @@
use std::cell::{Cell, RefCell};
use std::collections::{HashSet, VecDeque};
use std::iter;
use std::num::NonZeroUsize;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};
use super::super::main_thread::MainThreadMarker;
use super::super::DeviceId;
use super::backend;
@@ -14,20 +26,9 @@ use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawne
use crate::platform_impl::platform::window::Inner;
use crate::window::WindowId;
use js_sys::Function;
use std::cell::{Cell, RefCell};
use std::collections::{HashSet, VecDeque};
use std::iter;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::JsCast;
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};
pub struct Shared(Rc<Execution>);
pub(super) type EventHandler = dyn FnMut(Event);
pub(super) type EventHandler = dyn FnMut(Event<()>);
impl Clone for Shared {
fn clone(&self) -> Self {
@@ -136,12 +137,13 @@ impl Shared {
let document = window.document().expect("Failed to obtain document");
Shared(Rc::<Execution>::new_cyclic(|weak| {
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, local| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_proxy_wake_up(local);
}
})
.expect("`EventLoop` has to be created in the main thread");
let proxy_spawner =
WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_user_events(count, local)
}
})
.expect("`EventLoop` has to be created in the main thread");
Execution {
main_thread,
@@ -201,7 +203,7 @@ impl Shared {
// Set the event callback to use for the event loop runner
// This the event callback is a fairly thin layer over the user-provided callback that closes
// over a RootActiveEventLoop reference
pub(crate) fn set_listener(&self, event_handler: Box<EventHandler>) {
pub fn set_listener(&self, event_handler: Box<EventHandler>) {
{
let mut runner = self.0.runner.borrow_mut();
assert!(matches!(*runner, RunnerEnum::Pending));
@@ -243,21 +245,10 @@ impl Shared {
return;
}
let pointer_type = event.pointer_type();
if pointer_type != "mouse" {
return;
}
// chorded button event
let device_id = RootDeviceId(DeviceId(event.pointer_id()));
if let Some(button) = backend::event::mouse_button(&event) {
debug_assert_eq!(
pointer_type, "mouse",
"expect pointer type of a chorded button event to be a mouse"
);
let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
ElementState::Pressed
} else {
@@ -321,10 +312,6 @@ impl Shared {
return;
}
if event.pointer_type() != "mouse" {
return;
}
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
@@ -344,10 +331,6 @@ impl Shared {
return;
}
if event.pointer_type() != "mouse" {
return;
}
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
@@ -368,7 +351,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
device_id: RootDeviceId(DeviceId::dummy()),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Pressed,
@@ -386,7 +369,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
device_id: RootDeviceId(DeviceId::dummy()),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Released,
@@ -464,11 +447,11 @@ impl Shared {
self.send_events(iter::once(event));
}
// Add a user event to the event loop runner.
// Add a series of user events to the event loop runner
//
// This will schedule the event loop to wake up instead of waking it up immediately if its not
// running.
pub(crate) fn send_proxy_wake_up(&self, local: bool) {
pub(crate) fn send_user_events(&self, count: NonZeroUsize, local: bool) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
@@ -479,18 +462,14 @@ impl Shared {
if let Ok(RunnerEnum::Running(_)) =
self.0.runner.try_borrow().as_ref().map(Deref::deref)
{
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = queueMicrotask)]
fn queue_microtask(task: Function);
}
queue_microtask(
Closure::once_into_js({
self.window().queue_microtask(
&Closure::once_into_js({
let this = Rc::downgrade(&self.0);
move || {
if let Some(shared) = this.upgrade() {
Shared(shared).send_event(Event::UserWakeUp)
Shared(shared).send_events(
iter::repeat(Event::UserEvent(())).take(count.get()),
)
}
}
})
@@ -501,7 +480,7 @@ impl Shared {
}
}
self.send_event(Event::UserWakeUp);
self.send_events(iter::repeat(Event::UserEvent(())).take(count.get()))
}
// Add a series of events to the event loop runner
@@ -515,8 +494,13 @@ impl Shared {
// If we can run the event processing right now, or need to queue this and wait for later
let mut process_immediately = true;
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
// If the runner is attached but not running, we always wake it up.
Ok(RunnerEnum::Running(_)) => (),
Ok(RunnerEnum::Running(ref runner)) => {
// 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;
}
},
Ok(RunnerEnum::Pending) => {
// The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false;
@@ -644,10 +628,8 @@ impl Shared {
// Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle.
events.extend(
self.0
.proxy_spawner
.take()
.then_some(Event::UserWakeUp)
iter::repeat(Event::UserEvent(()))
.take(self.0.proxy_spawner.fetch())
.map(EventWrapper::from),
);
@@ -735,7 +717,7 @@ impl Shared {
// * The `register_redraw_request` closure.
// * The `destroy_fn` closure.
if self.0.event_loop_recreation.get() {
crate::event_loop::EventLoopBuilder::allow_event_loop_recreation();
crate::event_loop::EventLoopBuilder::<()>::allow_event_loop_recreation();
}
}
@@ -815,12 +797,12 @@ impl Shared {
}
pub(crate) enum EventWrapper {
Event(Event),
Event(Event<()>),
ScaleChange { canvas: Weak<RefCell<backend::Canvas>>, size: PhysicalSize<u32>, scale: f64 },
}
impl From<Event> for EventWrapper {
fn from(value: Event) -> Self {
impl From<Event<()>> for EventWrapper {
fn from(value: Event<()>) -> Self {
Self::Event(value)
}
}

View File

@@ -55,11 +55,7 @@ impl ActiveEventLoop {
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
}
pub(crate) fn run(
&self,
event_handler: Box<runner::EventHandler>,
event_loop_recreation: bool,
) {
pub fn run(&self, event_handler: Box<runner::EventHandler>, event_loop_recreation: bool) {
self.runner.event_loop_recreation(event_loop_recreation);
self.runner.set_listener(event_handler);
}
@@ -146,7 +142,7 @@ impl ActiveEventLoop {
}
});
let device_id = RootDeviceId(unsafe { DeviceId::dummy() });
let device_id = RootDeviceId(DeviceId::dummy());
runner.send_events(
iter::once(Event::WindowEvent {
@@ -182,7 +178,7 @@ impl ActiveEventLoop {
}
});
let device_id = RootDeviceId(unsafe { DeviceId::dummy() });
let device_id = RootDeviceId(DeviceId::dummy());
runner.send_events(
iter::once(Event::WindowEvent {
@@ -262,21 +258,6 @@ impl ActiveEventLoop {
});
canvas.on_cursor_move(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if has_focus.get() && modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
})
}
}
},
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
@@ -376,20 +357,6 @@ impl ActiveEventLoop {
);
canvas.on_mouse_press(
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
})
}
}
},
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
@@ -454,21 +421,6 @@ impl ActiveEventLoop {
);
canvas.on_mouse_release(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if has_focus.get() && modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
});
}
}
},
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
@@ -609,7 +561,7 @@ impl ActiveEventLoop {
window_id: RootWindowId(id),
event: WindowEvent::Resized(new_size),
});
runner.request_redraw(RootWindowId(id));
canvas.request_animation_frame();
}
}
},
@@ -662,6 +614,16 @@ impl ActiveEventLoop {
self.runner.listen_device_events(allowed)
}
pub fn system_theme(&self) -> Option<Theme> {
backend::is_dark_mode(self.runner.window()).map(|is_dark_mode| {
if is_dark_mode {
Theme::Dark
} else {
Theme::Light
}
})
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.runner.set_control_flow(control_flow)
}

View File

@@ -329,51 +329,29 @@ impl Canvas {
self.pointer_handler.on_cursor_enter(&self.common, handler)
}
pub fn on_mouse_release<MOD, M, T>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
pub fn on_mouse_release<M, T>(&mut self, mouse_handler: M, touch_handler: T)
where
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
self.pointer_handler.on_mouse_release(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
)
self.pointer_handler.on_mouse_release(&self.common, mouse_handler, touch_handler)
}
pub fn on_mouse_press<MOD, M, T>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
pub fn on_mouse_press<M, T>(&mut self, mouse_handler: M, touch_handler: T)
where
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
self.pointer_handler.on_mouse_press(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
Rc::clone(&self.prevent_default),
)
}
pub fn on_cursor_move<MOD, M, T, B>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
button_handler: B,
) where
MOD: 'static + FnMut(ModifiersState),
pub fn on_cursor_move<M, T, B>(&mut self, mouse_handler: M, touch_handler: T, button_handler: B)
where
M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator<Item = PhysicalPosition<f64>>),
T: 'static
+ FnMut(ModifiersState, i32, &mut dyn Iterator<Item = (PhysicalPosition<f64>, Force)>),
@@ -381,7 +359,6 @@ impl Canvas {
{
self.pointer_handler.on_cursor_move(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
button_handler,
@@ -483,7 +460,7 @@ impl Canvas {
pub(crate) fn handle_scale_change(
&self,
runner: &super::super::event_loop::runner::Shared,
event_handler: impl FnOnce(crate::event::Event),
event_handler: impl FnOnce(crate::event::Event<()>),
current_size: PhysicalSize<u32>,
scale: f64,
) {

View File

@@ -1,13 +1,15 @@
use crate::dpi::LogicalPosition;
use crate::event::{MouseButton, MouseScrollDelta};
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
use dpi::{LogicalPosition, PhysicalPosition, Position};
use smol_str::SmolStr;
use std::cell::OnceCell;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
use super::Engine;
bitflags::bitflags! {
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -95,42 +97,48 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
LogicalPosition { x: event.offset_x(), y: event.offset_y() }
}
// TODO: Remove this when Firefox supports correct movement values in coalesced events.
// TODO: Remove this when Firefox supports correct movement values in coalesced events and browsers
// have agreed on what coordinate space `movementX/Y` is using.
// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1753724>.
pub struct MouseDelta(Option<MouseDeltaInner>);
pub struct MouseDeltaInner {
old_position: LogicalPosition<f64>,
old_delta: LogicalPosition<f64>,
// See <https://github.com/w3c/pointerlock/issues/42>.
pub enum MouseDelta {
Chromium,
Gecko { old_position: LogicalPosition<f64>, old_delta: LogicalPosition<f64> },
Other,
}
impl MouseDelta {
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
// Firefox has wrong movement values in coalesced events, we will detect that by checking
// for `pointerrawupdate` support. Presumably an implementation of `pointerrawupdate`
// should require correct movement values, otherwise uncoalesced events might be broken as
// well.
Self((!has_pointer_raw_support(window) && has_coalesced_events_support(event)).then(|| {
MouseDeltaInner {
match super::engine(window) {
Some(Engine::Chromium) => Self::Chromium,
// Firefox has wrong movement values in coalesced events.
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
old_position: mouse_position(event),
old_delta: LogicalPosition {
x: event.movement_x() as f64,
y: event.movement_y() as f64,
},
}
}))
old_delta: LogicalPosition::new(
event.movement_x() as f64,
event.movement_y() as f64,
),
},
_ => Self::Other,
}
}
pub fn delta(&mut self, event: &MouseEvent) -> LogicalPosition<f64> {
if let Some(inner) = &mut self.0 {
let new_position = mouse_position(event);
let x = new_position.x - inner.old_position.x + inner.old_delta.x;
let y = new_position.y - inner.old_position.y + inner.old_delta.y;
inner.old_position = new_position;
inner.old_delta = LogicalPosition::new(0., 0.);
LogicalPosition::new(x, y)
} else {
LogicalPosition { x: event.movement_x() as f64, y: event.movement_y() as f64 }
pub fn delta(&mut self, event: &MouseEvent) -> Position {
match self {
MouseDelta::Chromium => {
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
},
MouseDelta::Gecko { old_position, old_delta } => {
let new_position = mouse_position(event);
let x = new_position.x - old_position.x + old_delta.x;
let y = new_position.y - old_position.y + old_delta.y;
*old_position = new_position;
*old_delta = LogicalPosition::new(0., 0.);
LogicalPosition::new(x, y).into()
},
MouseDelta::Other => {
LogicalPosition::new(event.movement_x(), event.movement_y()).into()
},
}
}
}
@@ -238,29 +246,6 @@ pub fn pointer_move_event(event: PointerEvent) -> impl Iterator<Item = PointerEv
}
}
// TODO: Remove when all browsers implement it correctly.
// See <https://github.com/rust-windowing/winit/issues/2875>.
pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
thread_local! {
static POINTER_RAW_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
POINTER_RAW_SUPPORT.with(|support| {
*support.get_or_init(|| {
#[wasm_bindgen]
extern "C" {
type PointerRawSupport;
#[wasm_bindgen(method, getter, js_name = onpointerrawupdate)]
fn has_on_pointerrawupdate(this: &PointerRawSupport) -> JsValue;
}
let support: &PointerRawSupport = window.unchecked_ref();
!support.has_on_pointerrawupdate().is_undefined()
})
})
}
// TODO: Remove when Safari supports `getCoalescedEvents`.
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {

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