Compare commits

..

148 Commits

Author SHA1 Message Date
John Nunley
d3657449ab Review comments
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
9382b7238c Eyepatch for the android linker error
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
73348f49d7 fmt
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
49a315870e Review comments
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
999a1f8733 Hacky way to exclude 1.65.0 on Android
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
023ab591b5 Disable test builds on Android
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
af32c5c298 Don't use CMD subselection on Android
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
0c4e725692 Fix compile error in xbuild-target
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
8671481db4 Once more unto the breach!
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
fe893e6690 Empty commit to re-run CI
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
5cfdb3fc42 Typo!
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
e30f112516 Fix malformed install-action call
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
f9a975e6e5 Use xbuild instead of cargo apk in the CI
In addition to cargo apk being deprecated, xbuild also has support for
ios.

Signed-off-by: John Nunley <dev@notgull.net>
2023-10-14 21:37:15 -07:00
John Nunley
b938fe9df5 Fix potentially unaligned references in X11 device
Fixes #3125
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-15 07:09:10 +04:00
Kirill Chibisov
b7e3649e8b Update SCTK to 0.18.0
The update is pretty minor, however we support now
`WindowEvent::Occluded` when xdg-shell v6 is available.

It also adds support for `Window::show_window_menu`.

Fixes #2927.
2023-10-15 06:49:57 +04:00
Kirill Chibisov
844269d017 Fix ndk deps versions 2023-10-15 06:39:18 +04:00
John Nunley
e41fac825c Update to new raw-window-handle strategy
Signed-off-by: John Nunley <dev@notgull.net>
Co-authored-by: TornaxO7 <tornax@proton.me>
2023-10-15 06:07:39 +04:00
Ryan Hileman
bbeacc46d5 feat: Implement set_cursor_hittest for X11 2023-10-13 20:42:07 -07:00
Kirill Chibisov
61581ebb4f Fix CHANGELOG entry for Event::MemoryWarning
While the changelog entries for beta releases doesn't really matter. The
change wasn't marked as breaking, while it is.

Fixes: 93f1000a0 (Add Occluded and MemoryWarning events for iOS/Android)
2023-10-13 02:02:00 +04:00
François
93f1000a05 Add Occluded and MemoryWarning events for iOS/Android
Hook `Occluded` event to foreground/background evens on iOS.

This commit also enabled the `MemoryWarning` event, since it's
emitted from the windowing system.

Co-authored-by: Dusty DeWeese <dustin.deweese@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-13 00:42:09 +04:00
YouKnow
1ea41a2ee2 Add Window::show_window_menu
Add a method to request a system menu. The implementation
is provided only on Windows for now.

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-11 01:46:16 +04:00
daxpedda
42c9b7e40e Fix reset to Poll after the event loop starts 2023-10-11 00:46:08 +04:00
baneyue
0960635895 On Wayland, fix MonitorHandle position 2023-10-11 00:44:52 +04:00
Kirill Chibisov
789a497980 Remove obsolete docs about wayland CSD env variable
The env variable was removed a while ago, yet it was still present in
the user docs.
2023-10-10 09:23:37 +04:00
daxpedda
fac6110cb6 Web: fix ControlFlow::WaitUntil to never wake up **before** the given time (#3133) 2023-10-09 12:31:02 +02:00
Dmitry Sharshakov
0363be4776 Add Window::set_blur
Allow clients to request blur behind their window, implemented on
Wayland for now.
2023-10-08 23:53:15 +04:00
daxpedda
f5dd1c008c Web: remove unnecessary usage of once_cell::unsync::Lazy (#3134) 2023-10-08 02:00:51 +02:00
daxpedda
48a1e84906 Update Clippy to v1.73 (#3135) 2023-10-08 01:21:46 +02:00
epimeletes
ee0db52ac4 Rename run_ondemand to run_on_demand 2023-10-04 01:24:42 +04:00
Fredrik Fornwall
c7cf0cfd83 Make DeviceId contain device id's on Android 2023-10-04 01:23:18 +04:00
Mads Marquart
8393d98940 Link to areweguiyet.com and arewegameyet.rs for extra deps 2023-10-04 01:22:14 +04:00
Mads Marquart
af247eac0f X11: Add #[deny(unsafe_op_in_unsafe_fn)] (#3121)
* X11: Add #[deny(unsafe_op_in_unsafe_fn)]

* Enable #![deny(unsafe_op_in_unsafe_fn)] everywhere
2023-09-30 21:43:41 +02:00
Mads Marquart
b2b4564a5f Windows: Add #[deny(unsafe_op_in_unsafe_fn)] (#3070) 2023-09-29 16:07:44 +02:00
Mads Marquart
cb58c49a90 Bump version on master (#3119)
This commit does not represent a release and only synchronizes CHANGELOG from the latest release.
2023-09-28 01:03:38 +02:00
Neil Macneale V
ffb46dd61f Fix transparent windows on X11 2023-09-26 01:05:15 +04:00
Kirill Chibisov
8c8fb39fcd Remove DeviceEvent::Text event
The event is never constructed inside the winit.
2023-09-23 18:40:23 +04:00
lucasmerlin
2422ea39d0 Pass force on touch events on android 2023-09-22 23:44:39 +04:00
daxpedda
878d832d24 Make ControlFlow::Wait the default (#3106) 2023-09-22 21:27:11 +02:00
Kirill Chibisov
e2e01e1fc6 Remove old docs about EventLoop::run 2023-09-22 13:46:52 +04:00
Pavel Strakhov
c8b685ddbc On X11, fix WaitUntil and Poll behavior
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-09-20 15:15:28 +04:00
StarStarJ
f10ae52385 Implement PartialOrd and Ord for MouseButton 2023-09-17 13:51:03 +04:00
Kirill Chibisov
e731041c15 Correct Wayland section in dpi docs
Fixes #3100.
2023-09-16 18:14:46 +04:00
daxpedda
9df7fc47a1 Install cargo-apk with the stable toolchain 2023-09-16 16:17:53 +04:00
daxpedda
992aeb0ca0 Ignore foreign-types* duplicate deps on macOS
The dependency is duplicated due to examples, yet we still need to
exclude checking it.

Fixes #3093.
2023-09-16 14:54:49 +04:00
daxpedda
0caba93b51 Rename PollType to PollStrategy (#3089) 2023-09-08 18:39:23 +02:00
John Nunley
c00c1e9eb7 Add an MSRV policy to the README (#3046) 2023-09-08 17:34:55 +02:00
daxpedda
83950acd5a Add Window.requestIdleCallback() support (#3084) 2023-09-07 12:12:35 +02:00
Fredrik Fornwall
b99403b1b9 Correct set_exit() -> exit() in the changelog (#3088) 2023-09-07 11:52:53 +02:00
John Nunley
4f0ce7201d Revert select_xkb_events to its previous impl
The new implementation of select_xkb_events apparently misconfigures
the server. This commit does a temporary fix by just reverting it to its
previous implementation.

This is temporary until I can figure out what Xlib is doing behind the
scenes or until I read xkbproto.pdf.

Fixes: #3079
Signed-off-by: John Nunley <dev@notgull.net>
2023-09-07 10:25:52 +04:00
daxpedda
e648169861 Move ControlFlow to EventLoopWindowTarget
Fixes #3042.
2023-09-07 10:25:04 +04:00
John Nunley
8fdd81ecef Allow the user to force X11 under Wayland
Use forced backend over the env variables. 

Signed-off-by: John Nunley <dev@notgull.net>
Fixes: #3057
2023-09-04 01:24:05 +04:00
daxpedda
7a2a2341c2 Remove T from EventLoopTargetWindow (#3081)
Co-authored-by: nerditation <12248559+nerditation@users.noreply.github.com>
2023-09-03 02:26:53 +02:00
Kirill Chibisov
d68d9eab38 Mark startup_notify unsafe functions as safe
They are safe, since they use the rust `std::env` stuff. Making them
safe lets downstream to determine that `std::env` is used and not the
`libc` env manipulation routines, which are unsafe.
2023-09-02 02:05:56 +04:00
Mads Marquart
a06ea45c0f Slightly reduce number of cfgs (#3071)
* Make Linux platforms less dependent on the root monitor handle

* Add various functions to the Wayland platform to reduce cfgs

* Don't use a cfg in listen_device_events

* Don't use a cfg in set_content_protected

* Fix instance of a target_os cfg
2023-09-01 23:14:16 +02:00
Kirill Chibisov
67b041e231 On Wayland, fix TouchPhase::Canceled sent for Move
Fixes #3035.
2023-09-01 02:14:34 +04:00
Mads Marquart
6dfc78fb50 Make EventLoopWindowTarget independent of the user type on Orbital (#3055) 2023-08-30 16:43:28 +02:00
Mads Marquart
477619c0a7 Ensure that winit initializes NSApplication (#3069) 2023-08-30 15:19:30 +02:00
Mads Marquart
5f1a4b65ad Fix missing quote (#3068) 2023-08-30 13:59:23 +02:00
John Nunley
bb9b629bc3 Implement X11 extensions using x11rb instead of Xlib
Removes Xlib code by replacing it with the x11rb equivalent,
the commit handles xrandr, xinput, xinput2, and xkb.

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-30 01:01:25 +04:00
daxpedda
0c8cf94a70 Web: Fullscreen Overhaul (#3063) 2023-08-29 09:28:30 +02:00
daxpedda
1dfca5a395 Enable event propagation (#3062) 2023-08-28 19:18:10 +02:00
Mads Marquart
7541220a41 Fix macOS deminiaturize (#3054) 2023-08-27 17:35:45 +02:00
Kirill Chibisov
7e11912d22 Lock the cargo-apk deps on CI 2023-08-27 19:05:45 +04:00
Mads Marquart
86baa1c99a Make iOS fully thread safe (#3045)
* macOS & iOS: Refactor EventWrapper

* macOS & iOS: Make EventLoopWindowTarget independent of the user event

* iOS: Use MainThreadMarker instead of marking functions unsafe

* Make iOS thread safe
2023-08-27 17:04:39 +02:00
Mads Marquart
d9f04780cc Improve CI caching, and give each job names
This improves CI performance by around 20% (down from 10 to 8 minutes).
2023-08-27 18:25:32 +04:00
daxpedda
67d3fd28f7 Move Event::RedrawRequested to WindowEvent (#3049) 2023-08-27 16:15:09 +02:00
daxpedda
a3cba838ea On Web, never return a MonitorHandle (#3051) 2023-08-26 18:56:44 +02:00
daxpedda
48abf52aac Use setTimeout() trick instead of Window.requestIdleCallback() (#3044) 2023-08-25 21:40:21 +02:00
Mads Marquart
68ef9f707e Use frame instead of visibleRect (#3043) 2023-08-24 22:52:11 +02:00
Mads Marquart
9979441c82 Fix recent CI failures (#3041)
* Fix new clippy lints

* Fix nightly documentation warnings
2023-08-24 18:29:31 +02:00
Kirill Chibisov
309e6aa85a Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.

This is a follow up to beta yanking
2023-08-16 16:34:36 +04:00
Kirill Chibisov
8b8556798e Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-08-16 13:21:09 +04:00
Kirill Chibisov
2d96480a89 Use beta versions of android crates 2023-08-16 12:31:23 +04:00
Kirill Chibisov
6caff77abb Bump MSRV to 1.65 2023-08-16 12:10:38 +04:00
Mads Marquart
af6c343d0e Improve macOS/iOS/Web thread safety
Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-08-14 23:19:57 +04:00
Kirill Chibisov
119462795a Pin android-activity git dependency 2023-08-14 23:10:32 +04:00
Kirill Chibisov
f801c4a00b Reexport raw-window-handle in window module
We use raw-window-handle extensive in the public API as well as we
force the users to use it to get some essential data for interop, thus
reexport it.

Fixes: #2913.
2023-08-14 11:24:45 +04:00
Kirill Chibisov
f9758528f6 Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users it's not desirable.

The inner panics were left only when they are used to `assert!` during
development.

This reverts commit 9f91bc413fe20618bd7090829832bb074aab15c3 which
reverted the original patch which was merged without a proper review.

Fixes: #500.
2023-08-13 23:20:09 +04:00
lucasmerlin
778d70c001 Fix touch force for Apple Pencil 2023-08-12 16:22:22 +04:00
John Nunley
dc973883c9 Add a way to embed the X11 window into another
Signed-off-by: John Nunley <dev@notgull.net>
Tested-by: Kirill Chibisov <contact@kchibisov.com>
2023-08-11 13:30:55 +04:00
Fredrik Fornwall
2233edb9a0 iOS: Use NSOperatingSystemVersion from icrate (#3019) 2023-08-09 15:57:20 +02:00
Robert Bragg
bd2f1e8312 Android: Support unicode character mapping + dead keys
Up until now the Android backend has been directly mapping key codes
which essentially just represent the "physical" cap of the key (quoted
since this also related to virtual keyboards).

Since we didn't account for any meta keys either it meant the backend
only supported a 1:1 mapping from key codes, which only covers a tiny
subset of characters. For example you couldn't type a colon since
there's no keycode for that and we didn't try and map Shift+Semicolon
into a colon character.

This has been tricky to support because the `NativeActivity` class doesn't
have direct access to the Java `KeyEvent` object which exposes a more
convenient `getUnicodeChar` API.

It is now possible to query a `KeyCharcterMap` for the device associated
with a `KeyEvent` via the `AndroidApp::device_key_character_map` API
which provides a binding to the SDK `KeyCharacterMap` API in Java:

 https://developer.android.com/reference/android/view/KeyCharacterMap

This is effectively what `getUnicodeChar` is implemented based on and is
a bit more general purpose.

`KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent`
into either a unicode character or dead key accent that can be combined
with the following key. This mapping is done based on the user's chosen
layout for the keyboard.

To enable support for key character maps the
`AndroidApp::input_events()` API was replaced by
`AndroidApp::input_events_iter()` which returns a (lending) iterator for
events. This was changed because the previous design made it difficult
to allow other AndroidApp APIs to be used while iterating events (mainly
because AndroidApp held a lock over the backend during iteration)
2023-08-08 02:56:42 +04:00
Kirill Chibisov
e9ebf1e5f4 Fix event loop not waking up due to repeat source
Force the wake up from the repeat source as well.

Fixes: cad327755 (On Wayland, reduce amount of spurious wakeups)
2023-08-07 09:35:59 +04:00
Kirill Chibisov
5f7955cb2b On X11, set visual_id in raw-window-handle
Fixes #2681.
2023-08-06 06:07:19 +04:00
Kirill Chibisov
793c535b01 Revert "Propagate error from EventLoop creation" (#3010)
This reverts commit ed26dd58fd.
The patched was merged with a review by accident.
2023-08-06 06:07:01 +04:00
Kirill Chibisov
ed26dd58fd Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users ints not desirable.

The inner panics were left only when they are used to `assert!` during
development.
2023-08-06 06:03:54 +04:00
John Nunley
584aab4cd0 Make with_x11_visual take ID instead of a pointer
At the moment, the with_x11_visual function takes a pointer and
immediately dereferences it to get the visual info inside. As it is safe
to pass a null pointer to this function, it is unsound. This commit
replaces the pointer parameter with a visual ID, and then uses that ID
to look up the actual visual under
the X11 setup. As this is what was already practically happening before,
this change shouldn't cause any performance downgrades.

This is a breaking change, but it's done in the name of soundness so it
should be okay. It should be trivial for end users to accommodate it,
as it's just a matter of getting the visual ID from the pointer to the
visual before passing it in.

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-06 01:58:23 +04:00
Kirill Chibisov
8100a6a584 Remove 'static requirement on run
There's no need to force the static on the users, given that internally
some backends were not using static in the first place.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-08-06 01:56:56 +04:00
Kirill Chibisov
cad3277550 On Wayland, reduce amount of spurious wakeups
Mark it as breaking, since some clients relied on that behavior, simply
because dispatching clients queue always woke up a winit, meaning that
they won't be able to use user events for this sake.
2023-08-06 01:09:59 +04:00
Mads Marquart
3c3a863cc9 Remove functionality already exposed through raw-window-handle
Nothing changed from the user point of view, other than they should
use the `raw-window-handle`, which is objectively better, given that
it reduces the amount of `cfg` guards in downstream code.
2023-08-05 22:56:22 +04:00
John Nunley
8a7e18aaf0 Increase test coverage for generic modules 2023-08-05 19:58:38 +04:00
dAxpeDDa
57fad2ce15 On Web, use requestAnimationFrame for RedrawRequested 2023-08-04 14:23:44 +04:00
Kirill Chibisov
7a58fe58ce On Wayland, use frame callbacks to throttle RedrawRequested
Throttle RedrawRequested events by the frame callbacks, so the users
could render at the display refresh rate.
2023-08-04 14:23:44 +04:00
Kirill Chibisov
38f28d5836 Add Window::on_present_notify to ack about drawing
That's a way to communicate to winit that you'll present to the window.
While it's a no-op for now, it'll be used to throttle drawing.
2023-08-04 14:23:44 +04:00
Diggory Hardy
189a0080a6 Export smol_str and impl Ord for Key
Fixes #2996.
2023-08-03 20:12:48 +04:00
Mads Marquart
b5aa96bea4 Update icrate to v0.0.4 (#2992) 2023-08-02 16:30:41 +02:00
Marijn Suijten
19e3906369 On X11, remove the now-unrefrenced events.rs source file
#2662 renamed `VirtualKeyCode` to `Key` yet references to the former
type still exist in `src/platform_impl/linux/x11/events.rs`.  As it
turns out the `mod events;` in `x11/mod.rs` was removed in the same PR,
but the file accidentally stuck around without being referenced anywhere
else.
2023-07-31 22:15:43 +04:00
Kirill Chibisov
9ac3259a79 Remove lifetime from the Event
Lifetimes don't work nicely when dealing with multithreaded environments
in the current design of the existing winit's event handling model, so
remove it in favor of `InnerSizeWriter` fences passed to client, so they
could try to update the size.

Fixes #1387.
2023-07-31 00:39:01 +04:00
Tobias Hunger
2b2dd6b65d On Windows, keep window maximized when setting size bounds (#2899) 2023-07-29 16:22:28 +02:00
Géraud-Loup
75173118b0 On Windows, add option to customize window class name (#2978) 2023-07-29 15:39:23 +02:00
Mads Marquart
e33d2bee6c Update objc2 version (#2936)
* Upgrade to objc2 v0.4.0 and icrate v0.0.3

* Fix `touchBar` method

* Use ClassType::alloc

* Use #[method_id(...)] functionality in declare_class!
2023-07-29 00:33:03 +02:00
Robert Bragg
ae7497e18f Remove RedrawEventsCleared + MainEventsCleared, and added AboutToWait
The idea that redraw events are dispatched with a specific ordering
that makes it possible to specifically report when we have finished
dispatching redraw events isn't portable and the way in which we
dispatched RedrawEventsCleared was inconsistent across backends.

More generally speaking, there is no inherent relationship between
redrawing and event loop iterations. An event loop may wake up at any
frequency depending on what sources of input events are being listened
to but redrawing is generally throttled and in some way synchronized
with the display frequency.

Similarly there's no inherent relationship between a single event loop
iteration and the dispatching of any specific kind of "main" event.

An event loop wakes up when there are events to read (e.g. input
events or responses from a display server / compositor) and goes back
to waiting when there's nothing else to read.

There isn't really a special kind of "main" event that is dispatched
in order with respect to other events.

What we can do more portably is emit an event when the event loop
is about to block and wait for new events.

In practice this is very similar to how MainEventsCleared was
implemented except it wasn't the very last event previously since
redraw events could be dispatched afterwards.

The main backend where we don't strictly know when we're going to
wait for events is Web (since the real event loop is internal to
the browser). For now we emulate AboutToWait on Web similar to how
MainEventsCleared was dispatched.

In practice most applications almost certainly shouldn't care about
AboutToWait because the frequency of event loop iterations is
essentially arbitrary and usually irrelevant.
2023-07-28 20:37:56 +04:00
Robert Bragg
935146d299 Rename LoopDestroyed to LoopExiting
Considering the possibility of re-running an event loop via run_ondemand
then it's more correct to say that the loop is about to exit without
assuming it's going to be destroyed.
2023-07-28 20:19:53 +04:00
François
755c533b08 iOS: Always set timer when polling to avoid slow waking (#2979)
* Always set timer when polling to avoid slow waking

* add comment and changelog

---------

Co-authored-by: Dusty DeWeese <dustin.deweese@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-07-28 17:52:24 +02:00
Robert Bragg
ae9b02e097 Add timeout argument to pump_events
This renames all internal implementations of pump_events_with_timeout
to pump_events and makes them public.

Since all platforms that support pump_events support timeouts there's
no need to have a separate API.
2023-07-28 03:04:32 +04:00
Robert Bragg
e6c7cc297d Windows: implement pump_events_with_timeout internally 2023-07-28 03:04:32 +04:00
Robert Bragg
b74cee8df1 MacOS: implement pump_events_with_timeout internally
This layers pump_events on a pump_events_with_timeout API, like we have
for Linux and Android.

This is just an internal implementation detail for now but we could
consider making pump_events_with_timeout public, or just making it so
that pump_events() takes the timeout argument.
2023-07-28 03:04:32 +04:00
Robert Bragg
e5eb253698 window_ondemand: wait for Destroyed event before exiting app
Considering the strict requirement that applications can't keep windows
across run_ondemand calls, this tries to make the window_ondemand example
explicitly wait for its Window to be destroyed before exiting each
run_ondemand iteration.

This updates the example to only `.set_exit()` after it gets a
`Destroyed` event after the Window has been dropped.

On Windows this works to ensure the Window is destroyed before the
example waits for 5 seconds.

Unfortunately though:
1. The Wayland backend doesn't emit `Destroyed` events for windows
2. The macOS backend emits `Destroyed` events before the window is
   really destroyed.

and so the example isn't currently portable.
2023-07-28 03:04:32 +04:00
Robert Bragg
ec11b4877f Linux: Sync with server/compositor before exiting run_ondemand
Although we document that applications can't keep windows between
separate run_ondemand calls it's possible that the application has only
just dropped their windows and we need to flush these requests to the
server/compositor.

This fixes the window_ondemand example - by ensuring the window from
the first loop really is destroyed before waiting for 5 seconds
and starting the second loop.
2023-07-28 03:04:32 +04:00
Robert Bragg
9e46dffcc5 Update CHANGELOG.md 2023-07-28 03:04:32 +04:00
Robert Bragg
7501039d57 Add examples/window_ondemand
A minimal example that shows an application running the event loop more
than once via `run_ondemand`

There is a 5 second delay between each run to help highlight problems
with destroying the window from the first loop.
2023-07-28 03:04:32 +04:00
Robert Bragg
289ce32d77 Add examples/window_pump_events
A minimal example of an application based on an external event loop that
calls `pump_events` for each iteration of the external loop.
2023-07-28 03:04:32 +04:00
Robert Bragg
0d366ffbda Re-work event loop run() API so it can return a Result
This re-works the portable `run()` API that consumes the `EventLoop` and
runs the loop on the calling thread until the app exits.

This can be supported across _all_ platforms and compared to the
previous `run() -> !` API is now able to return a `Result` status on all
platforms except iOS and Web. Fixes: #2709

By moving away from `run() -> !` we stop calling `std::process::exit()`
internally as a means to kill the process without returning which means
it's possible to return an exit status and applications can return from
their `main()` function normally.

This also fixes Android support where an Activity runs in a thread but
we can't assume to have full ownership of the process (other services
could be running in separate threads).

Additionally all examples have generally been updated so that `main()`
returns a `Result` from `run()`

Fixes: #2709
2023-07-28 03:04:32 +04:00
Robert Bragg
a6f414d732 Remove EventLoopExtRunReturn 2023-07-28 03:04:32 +04:00
Robert Bragg
c47d0846fa Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
Wayland:

I found the calloop abstraction a little awkward to work with while I was
trying to understand why there was surprising workaround code in the wayland
backend for manually dispatching pending events.

Investigating this further it looks like there may currently be several issues
with the calloop WaylandSource (with how prepare_read is used and with (not)
flushing writes before polling)

Considering the current minimal needs for polling in all winit backends I do
personally tend to think it would be simpler to just own the responsibility for
polling more directly, so the logic for wayland-client `prepare_read` wouldn't
be in a separate crate (and in this current situation would also be easier to fix)

I've tried to maintain the status quo with calloop + workarounds.

X11:

I found that the recent changes (4ac2006cbc) to port the X11 backend
from mio to calloop lost the ability to check for pending events before
needing to poll/dispatch. (The `has_pending` state being queried
before dispatching() was based on state that was filled in during
dispatching)

As part of the rebase this re-introduces the PeekableReceiver and
WakeSender which are small utilities on top of
`std::sync::mpsc::channel()`. This adds a calloop `PingSource`
so we can use a `Ping` as a generic event loop waker.

For taking into account false positive wake ups the X11 source now
tracks when the file descriptor is readable so after we poll via
calloop we can then specifically check if there are new X11 events
or pending redraw/user events when deciding whether to skip the
event loop iteration.
2023-07-28 03:04:32 +04:00
Robert Bragg
461efaf99f MacOS: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
The implementation of `pump_events` essentially works by hooking into the
`RunLoopObserver` and requesting that the app should be stopped the next time
that the `RunLoop` prepares to wait for new events.

Originally I had thought I would poke the `CFRunLoop` for the app directly and
I was originally going to implement `pump_events` based on a timeout which I'd
seen SDL doing.

I found that `[NSApp run]` wasn't actually being stopped by asking the RunLoop
to stop directly and inferred that `NSApp run` will actually catch this and
re-start the loop.

Hooking into the observer and calling `[NSApp stop]` actually seems like a
better solution that doesn't need a hacky constant timeout.

The end result is quite similar to what happens with existing apps that
call `run_return` inside an external loop and cause the loop to exit for
each iteration (that also results in the `NSApp` stopping each
iteration).
2023-07-28 03:04:32 +04:00
Robert Bragg
420840278b Windows: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
A surprising amount of work was required to enable these extensions
on Windows.

I had originally assumed that pump_events was going to be very similar
to run except would use PeekMessageW instead of GetMessageW to avoid
blocking the external loop but I found the Windows backend broke
several assumptions I had.

Overall I think these changes can hopefully be considered a quite a
significant simplification (I think it's a net deletion of a fair amount
of code) and I think it also helps bring it into slightly closer alignment
with other backends too

Key changes:
- I have removed the `wait_thread` that was a fairly fiddly way of handling
  `ControlFlow::WaitUntil` timeouts in favor of using `SetTimer` which works
  with the same messages picked up by `GetMessage` and `PeekMessage`.
- I have removed the ordering guarantees between `MainEventsCleared`,
  `RedrawRequested` and `RedrawEventsCleared` events due to the complexity in
  maintaining this artificial ordering, which is already not supported
  consistently across backends anyway (in particular this ordering already
  isn't compatible with how MacOS / iOS work).
- `RedrawRequested` events are now directly dispatched via `WM_PAINT` messages
  - comparable to how `RedrawRequested` is dispatched via `drawRect` in the
  MacOS backend.
- I have re-worked how `NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`
  get dispatched to be more in line with the MacOS backend and also more in line
  with how we have recently discussed defining them for all platforms.

  `NewEvents` is conceptually delivered when the event loop "wakes up" and
  `MainEventsCleared` gets dispatched when the event loop is about to ask the
  OS to wait for new events.

  This is a more portable model, and is already how these events work in the
  MacOS backend.

  `RedrawEventsCleared` are just delivered after `MainEventsCleared` but this
  event no longer has a useful meaning.

Probably the most controversial thing here is that this "breaks" the ordering
rules for redraw event handling, but since my changes interacted with how the
order is maintained I was very reluctant to figure out how to continue
maintaining something that we have recently been discussing changing:

https://github.com/rust-windowing/winit/issues/2640.

Additionally, since the MacOS backend already doesn't strictly maintain this
order it's somewhat academic to see this as a breakage if Winit applications
can't really rely on it already.

This updates the documentation for `request_redraw()` to reflect that we
no longer guarantee that `RedrawRequested` events must be dispatched
after `MainEventsCleared`.
2023-07-28 03:04:32 +04:00
Robert Bragg
f5e73b0af4 Android: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand 2023-07-28 03:04:32 +04:00
Robert Bragg
f40b5f0dad Add EventLoopExtPumpEvents and EventLoopExtRunOnDemand
This adds two new extensions for running a Winit event loop which will
replace `EventLoopExtRunReturn`

The `run_return` API is trying to solve multiple problems and address
multiple, unrelated, use cases but in doing so it is not succeeding
at addressing any of them fully.

The notable use cases we have are:
1. Applications want to be able to implement their own external
   event loop and call some Winit API to poll / pump events, once
   per iteration of their own loop, without blocking the outer,
   external loop. Addressing #2706
2. Applications want to be able to re-run separate instantiations
   of some Winit-based GUI and want to allow the event loop to exit with
   a status, and then later be able to run the loop again for a new
   instantiation of their GUI. Addressing #2431

It's very notable that these use cases can't be supported across
all platforms and so they are extensions, similar to
`EventLoopExtRunReturn`

The intention is to support these extensions on:
- Windows
- Linux (X11 + Wayland)
- macOS
- Android

These extensions aren't compatible with Web or iOS though.

Each method of running the loop will behave consistently in terms of how
`NewEvents(Init)`, `Resumed` and `LoopDestroyed` events are dispatched
(so portable application code wouldn't necessarily need to have any awareness
of which method of running the loop was being used)

Once all backends have support for these extensions then we can
remove `EventLoopExtRunReturn`

For simplicity, the extensions are documented with the assumption that
the above platforms will be supported.

This patch makes no functional change, it only introduces these new
extensions so we can then handle adding platform-specific backends
in separate pull requests, so the work can be landed in stages.
2023-07-28 03:04:32 +04:00
daxpedda
c91402efb9 Correctly detect that we don't support Emscripten (#2971) 2023-07-22 18:31:40 +02:00
John Nunley
43acf7f42f Replace libc with rustix in some modules
Unfortunately this isn't a total removal, for two reasons:

- We still need "libc" for the Xlib XIM implementation, for locales.
- BSD requires libc to check for main-threadedness.

First one we can likely resolve in the near future, not so sure about
the second one without using some weird pthreads trick.
2023-07-22 09:32:27 +00:00
Venceslas Duet
c62e64060b On Windows, add drag_resize_window method support (#2966) 2023-07-21 20:01:56 +02:00
Kirill Chibisov
f7a84a5b50 Add platform::startup_notify for Wayland/X11
The utils in this module should help the users to activate the windows
they create, as well as manage activation tokens environment variables.

The API is essential for Wayland in the first place, since some
compositors may decide initial focus of the window based on whether
the activation token was during the window creation.

Fixes #2279.

Co-authored-by: John Nunley <jtnunley01@gmail.com>
2023-07-20 13:16:51 +00:00
Venceslas Duet
89aa7cc06e On Wayland, fix Window::is_decorated with CSD 2023-07-18 11:57:33 +00:00
Kirill Chibisov
b166e1ff13 On Wayland, make the CSD frame double click reliable
It was discovered that on GNOME the click sometimes being swallowed
by the mutter's `wl_pointer::enter/leave` sequences. This was happening
due to `xdg_toplevel::move` making the pointer to leave the surface.

To make handling of that more robust, we could start the
`xdg_toplevel::move` when the actual pointer motion is being performed.

Links: https://github.com/alacritty/alacritty/issues/7011
Links: https://gitlab.gnome.org/GNOME/mutter/-/issues/2669#note_1790825
2023-07-15 08:09:28 +00:00
Kirill Chibisov
97434d8d80 On macOS, add a way to query amount of tabbed windows
This should provide a way to iterate all the tabs and select the last
tab. The tab indicies are now zero based as any other sane index.

Follow-up-to: c5941d105f (add tabbing API)
2023-07-14 08:01:40 +00:00
Sam
06fb089633 On macOS, set that we prefer tabbing
Winit now supports tabbing identifiers, thus set that we prefer tabbing,
in particular it'll make windows tab when using the same tabbing identifiers,
which is desirable for the end users.
2023-07-14 04:14:04 +00:00
Kirill Chibisov
4d6dbea74c On macOS, move automatic tabbing setting to ELWT
Those are global for the application, so it's better to keep them
on EventLoopWindowTarget.
2023-07-13 15:55:51 +00:00
Kirill Chibisov
c5941d105f On macOS, add tabbing APIs
This should let the users control macOS tabbing and allow to create
windows in tab.

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
2023-07-13 06:52:34 +00:00
daxpedda
b63164645b On Web, add WindowBuilderExtWebSys::with_append() (#2953) 2023-07-12 17:11:52 +02:00
daxpedda
5b5ebc25d8 Improve documentation of WindowBuilderExtWebSys methods (#2952) 2023-07-12 17:11:17 +02:00
John Nunley
d7ec899d69 Replace parts of the Xlib backend with x11-rb 2023-07-12 07:59:12 +00:00
daxpedda
5379d60e4d On Web, remove Window::is_dark_mode() (#2951) 2023-07-11 18:26:32 +02:00
daxpedda
44e2f95331 Fix mentions of Wasm (#2950) 2023-07-11 18:26:00 +02:00
daxpedda
3b2d1a7643 On Web, implement and fix missing methods on Window(Builder) (#2949) 2023-07-11 13:14:40 +02:00
daxpedda
50b17a3907 Add Fullscreen API compatibility for Safari (#2948) 2023-07-11 00:34:02 +02:00
daxpedda
af26f01b95 On Web, cache commonly used values (#2947) 2023-07-11 00:11:06 +02:00
daxpedda
c4d70d75c1 Increase accuracy of various Web APIs (#2946) 2023-07-10 23:55:43 +02:00
daxpedda
db8de03142 Improve Web specific documentation for various APIs (#2941)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-07-10 12:50:28 +02:00
Kirill Chibisov
ff0ce9d065 Rename Window::set_inner_size to Window::request_inner_size
Some systems could resize the window immediately and we'd rather
inform the users right away if that was the case, so they could
create e.g. EGLSurface without waiting for resize, which is really
important for Wayland.

Fixes #2868.
2023-07-10 04:02:26 +00:00
daxpedda
42e492cde8 Fix touch location accuracy (#2944) 2023-07-10 02:17:36 +02:00
daxpedda
5e0e1e96bc On Web, implement WindowEvent::Occluded (#2940) 2023-07-10 02:02:38 +02:00
Imbris
bd890e69aa On X11, avoid false positive key repeats
Instead of a single `bool` indicating that a key press has occured and
no key has been released since then, we store the scancode of the last
pressed key (if it is a key that repeats when held). This fixes a bug
where pressing a new key while one is already held down will be flagged
as a repeat even though it is obviously not a repeat.
2023-07-09 17:05:49 +00:00
Mads Marquart
bca57ed0b4 Stop using &mut in Objective-C delegate methods (#2925)
* Make iOS declared classes not use &mut

* Prepare `init` methods for not having access to &mut self

* Prepare WinitWindow methods for not having access to &mut self

* Convert a bit of WinitView's to use interior mutability

* Convert a bit more of WinitView's to use interior mutability

* Convert the rest of WinitView to use interior mutability

* Use interior mutability instead of a Mutex for the CursorState

* Use interior mutability in WinitWindowDelegate
2023-07-08 22:36:42 +03:00
daxpedda
4652d48105 Don't unnecessarily clone canvas on Web (#2934) 2023-07-08 17:05:05 +02:00
daxpedda
96c0b267e2 Fix typos on Web (#2933) 2023-07-08 16:47:31 +02:00
StarStarJ
81fd39485f Implement PartialOrd/Ord for KeyCode/NativeKeyCode 2023-07-01 19:07:35 +04:00
Kirill Chibisov
6178acede8 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-07-01 00:10:02 +04:00
135 changed files with 5346 additions and 4157 deletions

View File

@@ -6,146 +6,184 @@ on:
branches: [master]
jobs:
Check_Formatting:
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
- uses: dtolnay/rust-toolchain@stable
with:
rust-version: stable
components: rustfmt
- name: Check Formatting
run: cargo +stable fmt --all -- --check
run: cargo fmt -- --check
tests:
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.65.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, }
- { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, }
- { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
include:
# Android is tested on stable-3
- toolchain: '1.69.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' }
- toolchain: 'stable'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' }
- toolchain: 'nightly'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity' }
env:
# Set more verbose terminal output
CARGO_TERM_VERBOSE: true
RUST_BACKTRACE: 1
# Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings'
RUSTDOCFLAGS: '--deny=warnings'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
steps:
- uses: actions/checkout@v3
- name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after
# the cache has been downloaded.
#
# This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v3
with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-never-intended-to-be-found
restore-keys: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Install xbuild
uses: taiki-e/install-action@v2
if: contains(matrix.platform.target, 'android') || contains(matrix.platform.target, 'ios')
with:
tool: xbuild@0.2.0
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install LLVM tools for Android
if: contains(matrix.platform.target, 'android')
run: sudo apt-get update && sudo apt-get install lld llvm
- name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items
- name: Build crate
run: cargo build $OPTIONS
- name: Build and package crate for Android
if: contains(matrix.platform.target, 'android')
run: x build -p android-xbuild-target --platform android --arch arm64
- name: Build and package crate for iOS
if: contains(matrix.platform.target, 'ios')
run: x build -p ios-xbuild-target --platform ios --arch arm64
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
!contains(matrix.platform.target, 'android') &&
matrix.toolchain != '1.65.0'
run: cargo test --no-run $OPTIONS
- name: Run tests
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo test $OPTIONS
- name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
!contains(matrix.platform.target, 'android') &&
matrix.toolchain != '1.65.0'
run: cargo test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo test $OPTIONS --features serde
# See restore step above
- name: Save cache of cargo folder
uses: actions/cache/save@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny:
name: cargo-deny
name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- aarch64-apple-ios
- aarch64-linux-android
- i686-pc-windows-gnu
- i686-pc-windows-msvc
- i686-unknown-linux-gnu
- wasm32-unknown-unknown
- x86_64-apple-darwin
- x86_64-apple-ios
- x86_64-pc-windows-gnu
- x86_64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-redox
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: x86_64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'web', target: wasm32-unknown-unknown }
- { name: 'Windows', target: x86_64-pc-windows-gnu }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform }}
tests:
name: Tests
strategy:
fail-fast: false
matrix:
rust_version: ['1.65.0', stable, nightly]
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
- { target: i686-pc-windows-msvc, os: windows-latest, }
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { target: aarch64-linux-android, os: ubuntu-latest, options: -p winit, cmd: 'apk --', features: "android-native-activity" }
- { target: x86_64-unknown-redox, os: ubuntu-latest, }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
- { target: wasm32-unknown-unknown, os: windows-latest, }
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
CMD: ${{ matrix.platform.cmd }}
RUSTDOCFLAGS: -Dwarnings
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v3
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1
with:
path: ~/.cargo
key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk
- name: Check documentation
shell: bash
run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
- name: Build crate
shell: bash
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build tests
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.65.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests
shell: bash
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.65.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Lint with clippy
shell: bash
if: (matrix.rust_version == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
- name: Build tests with serde enabled
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.65.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.65.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
arguments: --all-features --target ${{ matrix.platform.target }}

View File

@@ -8,6 +8,33 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased
- Renamed `EventLoopExtRunOnDemand` / `run_ondemand` to `EventLoopExtRunOnDemand` / `run_on_demand`.
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
- On Web, never return a `MonitorHandle`.
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
- On macOS, fix crash in `window.set_minimized(false)`.
- On Web, enable event propagation and let `DeviceEvent`s appear after `WindowEvent`s.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
- Mark `startup_notify` unsafe functions as safe.
- Fix a bug where Wayland would be chosen on Linux even if the user specified `with_x11`. (#3058)
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
- Implement `PartialOrd` and `Ord` for `MouseButton`.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
- **Breaking:** remove `DeviceEvent::Text`.
- On Android, fix `DeviceId` to contain device id's.
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- **Breaking** add `Event::MemoryWarning`; implemented on iOS/Android.
- On Wayland, support `Occluded` event with xdg-shell v6
# 0.29.1-beta
- **Breaking:** Bump `ndk` version to `0.8.0-beta.0`, ndk-sys to `v0.5.0-beta.0`, `android-activity` to `0.5.0-beta.1`.
@@ -70,6 +97,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On iOS, add force data to touch events when using the Apple Pencil.
- On Android, add force data to touch events.
# 0.29.0-beta.0
@@ -154,6 +182,10 @@ And please only add new entries to the top of this list, right below the `# Unre
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events
on Wayland, X11, Windows, macOS and Web.
# 0.28.7
- Fix window size sometimes being invalid when resizing on macOS 14 Sonoma.
# 0.28.6
- On macOS, fixed memory leak when getting monitor handle.

View File

@@ -13,7 +13,7 @@ categories = ["gui"]
rust-version = "1.65.0"
[package.metadata.docs.rs]
features = ["serde"]
features = ["rwh_04", "rwh_05", "rwh_06", "serde"]
default-target = "x86_64-unknown-linux-gnu"
# These are all tested in CI
targets = [
@@ -35,9 +35,9 @@ targets = [
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
@@ -55,7 +55,9 @@ cursor-icon = "1.0.0"
log = "0.4"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
raw_window_handle = { package = "raw-window-handle", version = "0.5", features = ["std"] }
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0"
@@ -68,16 +70,16 @@ softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release android-activity 0.5 release
android-activity = "0.5.0-beta.1"
ndk = "0.8.0-beta.0"
ndk-sys = "0.5.0-beta.0"
android-activity = "=0.5.0-beta.1"
ndk = "=0.8.0-beta.0"
ndk-sys = "=0.5.0-beta.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "0.4.1"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22.3"
core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.0.4"
@@ -140,21 +142,22 @@ features = [
]
[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.3", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
fnv = { version = "1.0.3", optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.6.0", default_features = false, optional = true }
wayland-client = { version = "0.30.0", optional = true }
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
calloop = "0.10.5"
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "xinput", "xkb"], optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0"
memmap2 = { version = "0.5.0", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }
@@ -164,6 +167,8 @@ redox_syscall = "0.3"
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'console',
'CssStyleDeclaration',
'Document',
@@ -179,6 +184,8 @@ features = [
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
@@ -205,5 +212,7 @@ web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[workspace]
members = [
"xbuild/android-target",
"xbuild/ios-target",
"run-wasm",
]

View File

@@ -182,6 +182,7 @@ Legend:
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
@@ -205,7 +206,7 @@ Legend:
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ | |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |

View File

@@ -13,7 +13,7 @@ winit = "0.29.1-beta"
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do.
## Contact Us
@@ -35,22 +35,21 @@ another library.
```rust
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
event_loop.set_control_flow(ControlFlow::Wait);
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
event_loop.run(move |event, elwt| {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
} if window_id == window.id() => elwt.exit(),
_ => (),
}
});
@@ -67,6 +66,35 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
## MSRV Policy
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following
formula:
```
min(sid, stable - 3)
```
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
`stable` is the latest stable version of Rust. This bound may be broken in the
event of a major ecosystem shift or a security vulnerability.
[Debian Sid]: https://packages.debian.org/sid/rustc
The exception to this is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this
restriction.
All crates in the [`rust-windowing`] organizations have the
same MSRV policy.
[`rust-windowing`]: https://github.com/rust-windowing
### Platform-specific usage
#### Wayland
@@ -95,7 +123,7 @@ book].
#### Android
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
Native Android applications need some form of "glue" crate that is responsible
for defining the main entry point for your Rust application as well as tracking
@@ -114,6 +142,7 @@ clash.
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29.1-beta | `android-activity = "0.5.0-beta.1"` |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
@@ -150,7 +179,7 @@ class. Your application _must_ specify the base class it needs via a feature fla
For example, add this to Cargo.toml:
```toml
winit = { version = "0.28", features = [ "android-native-activity" ] }
winit = { version = "0.29.1-beta", features = [ "android-native-activity" ] }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11.0"
@@ -181,7 +210,7 @@ For more details, refer to these `android-activity` [example applications](https
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.28", features = [ "android-native-activity" ] }`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.1-beta", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).

View File

@@ -9,4 +9,5 @@ disallowed-methods = [
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "icrate::AppKit::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." },
]

View File

@@ -31,16 +31,16 @@ multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "nix" }, # differing version - as of 2023-03-02 whis can be solved with `cargo update && cargo update -p calloop --precise 0.10.2`
{ name = "memoffset"}, # due to different nix versions.
{ name = "memmap2" }, # sctk uses a different version until the next update
{ name = "libloading" }, # x11rb uses a different version until the next update
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 and https://github.com/rustwasm/wasm-bindgen/issues/3390
{ name = "num_enum"}, # See above ^, waiting for release
{ name = "num_enum_derive"},# See above ^, waiting for release
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
{ 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
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392
{ name = "num_enum"}, # See above ^, waiting for release
{ name = "num_enum_derive"}, # See above ^, waiting for release
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
{ name = "foreign-types-shared" }, # https://github.com/servo/core-foundation-rs/issues/634
{ name = "foreign-types" }, # See above ^, waiting for release
]
skip-tree = []

View File

@@ -1,16 +1,23 @@
#[cfg(any(x11_platform, macos_platform, windows_platform))]
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[path = "util/fill.rs"]
mod fill;
#[cfg(any(x11_platform, macos_platform, windows_platform))]
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use winit::{
dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
window::raw_window_handle::HasRawWindowHandle,
event_loop::{EventLoop, EventLoopWindowTarget},
raw_window_handle::HasRawWindowHandle,
window::{Window, WindowBuilder, WindowId},
};
@@ -19,7 +26,7 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>,
) {
let parent = parent.raw_window_handle();
let parent = parent.raw_window_handle().unwrap();
let mut builder = WindowBuilder::new()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
@@ -46,14 +53,12 @@ fn main() -> Result<(), impl std::error::Error> {
println!("parent window: {parent_window:?})");
event_loop.run(move |event: Event<()>, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
event_loop.run(move |event: Event<()>, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
windows.clear();
*control_flow = ControlFlow::Exit;
elwt.exit();
}
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created
@@ -70,19 +75,23 @@ fn main() -> Result<(), impl std::error::Error> {
},
..
} => {
spawn_child_window(&parent_window, event_loop, &mut windows);
spawn_child_window(&parent_window, elwt, &mut windows);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
} else if let Event::RedrawRequested(wid) = event {
if let Some(window) = windows.get(&wid) {
fill::fill_window(window);
}
}
})
}
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
#[cfg(not(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
)))]
fn main() {
panic!("This example is supported only on x11, macOS, and Windows.");
panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled.");
}

View File

@@ -9,7 +9,7 @@ use web_time as time;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
keyboard::Key,
window::WindowBuilder,
};
@@ -47,7 +47,7 @@ fn main() -> Result<(), impl std::error::Error> {
let mut wait_cancelled = false;
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
use winit::event::StartCause;
println!("{event:?}");
match event {
@@ -93,6 +93,9 @@ fn main() -> Result<(), impl std::error::Error> {
}
_ => (),
},
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
@@ -101,25 +104,24 @@ fn main() -> Result<(), impl std::error::Error> {
}
match mode {
Mode::Wait => control_flow.set_wait(),
Mode::Wait => elwt.set_control_flow(ControlFlow::Wait),
Mode::WaitUntil => {
if !wait_cancelled {
control_flow.set_wait_until(time::Instant::now() + WAIT_TIME);
elwt.set_control_flow(ControlFlow::WaitUntil(
time::Instant::now() + WAIT_TIME,
));
}
}
Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME);
control_flow.set_poll();
elwt.set_control_flow(ControlFlow::Poll);
}
};
if close_requested {
control_flow.set_exit();
elwt.exit();
}
}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
_ => (),
}
})

View File

@@ -19,40 +19,33 @@ fn main() -> Result<(), impl std::error::Error> {
let mut cursor_idx = 0;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
WindowEvent::CloseRequested => {
elwt.exit();
}
_ => (),
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

@@ -22,56 +22,52 @@ fn main() -> Result<(), impl std::error::Error> {
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
let result = match key {
Key::Escape => {
control_flow.set_exit();
event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
let result = match key {
Key::Escape => {
elwt.exit();
Ok(())
}
Key::Character(ch) => match ch.to_lowercase().as_str() {
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
"a" => window.set_cursor_grab(CursorGrabMode::None),
"h" => {
window.set_cursor_visible(modifiers.shift_key());
Ok(())
}
Key::Character(ch) => match ch.to_lowercase().as_str() {
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
"a" => window.set_cursor_grab(CursorGrabMode::None),
"h" => {
window.set_cursor_visible(modifiers.shift_key());
Ok(())
}
_ => Ok(()),
},
_ => Ok(()),
};
},
_ => Ok(()),
};
if let Err(err) = result {
println!("error: {err}");
}
if let Err(err) = result {
println!("error: {err}");
}
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
DeviceEvent::Button { button, state } => match state {
ElementState::Pressed => println!("mouse button {button} pressed"),
ElementState::Released => println!("mouse button {button} released"),
},
_ => (),
},
Event::RedrawRequested(_) => fill::fill_window(&window),
}
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
WindowEvent::RedrawRequested => fill::fill_window(&window),
_ => (),
}
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
DeviceEvent::Button { button, state } => match state {
ElementState::Pressed => println!("mouse button {button} pressed"),
ElementState::Released => println!("mouse button {button} released"),
},
_ => (),
},
_ => (),
})
}

View File

@@ -40,20 +40,19 @@ fn main() -> Result<(), impl std::error::Error> {
}
});
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
event_loop.run(move |event, elwt| match event {
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
}
_ => (),
})
}

View File

@@ -20,18 +20,16 @@ fn main() -> Result<(), impl std::error::Error> {
let mut switched = false;
let mut entered_id = window_2.id();
let mut cursor_location = None;
event_loop.run(move |event, _, control_flow| match event {
event_loop.run(move |event, elwt| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Switch which window is to be dragged by pressing \"x\".")
}
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => cursor_location = Some(position),
WindowEvent::MouseInput { state, button, .. } => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
@@ -40,7 +38,15 @@ fn main() -> Result<(), impl std::error::Error> {
&window_1
};
window.drag_window().unwrap()
match (button, state) {
(MouseButton::Left, ElementState::Pressed) => window.drag_window().unwrap(),
(MouseButton::Right, ElementState::Released) => {
if let Some(position) = cursor_location {
window.show_window_menu(position);
}
}
_ => (),
}
}
WindowEvent::CursorEntered { .. } => {
entered_id = window_id;
@@ -54,20 +60,35 @@ fn main() -> Result<(), impl std::error::Error> {
..
},
..
} if c == "x" => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
} => match c.as_str() {
"x" => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
"d" => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
window.set_decorations(!window.is_decorated());
}
_ => (),
},
WindowEvent::RedrawRequested => {
if window_id == window_1.id() {
fill::fill_window(&window_1);
} else if window_id == window_2.id() {
fill::fill_window(&window_2);
}
}
_ => (),
},
Event::RedrawRequested(wid) => {
if wid == window_1.id() {
fill::fill_window(&window_1);
} else if wid == window_2.id() {
fill::fill_window(&window_2);
}
}
_ => (),
})
}

View File

@@ -52,12 +52,10 @@ fn main() -> Result<(), impl std::error::Error> {
println!("- I\tToggle mIn size limit");
println!("- A\tToggle mAx size limit");
event_loop.run(move |event, elwt, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
@@ -67,7 +65,7 @@ fn main() -> Result<(), impl std::error::Error> {
},
..
} => match key {
Key::Escape => control_flow.set_exit(),
Key::Escape => elwt.exit(),
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character(ch) => match ch.to_lowercase().as_str() {
@@ -155,12 +153,11 @@ fn main() -> Result<(), impl std::error::Error> {
},
_ => (),
},
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => {}
}
})
}

View File

@@ -22,70 +22,65 @@ fn main() -> Result<(), impl std::error::Error> {
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => {
// `CloseRequested` is sent when the close button on the window is pressed (or
// through whatever other mechanisms the window manager provides for closing a
// window). If you don't handle this event, the close button won't actually do
// anything.
match event {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => {
// `CloseRequested` is sent when the close button on the window is pressed (or
// through whatever other mechanisms the window manager provides for closing a
// window). If you don't handle this event, the close button won't actually do
// anything.
// A common thing to do here is prompt the user if they have unsaved work.
// Creating a proper dialog box for that is far beyond the scope of this
// example, so here we'll just respond to the Y and N keys.
println!("Are you ready to bid your window farewell? [Y/N]");
close_requested = true;
// A common thing to do here is prompt the user if they have unsaved work.
// Creating a proper dialog box for that is far beyond the scope of this
// example, so here we'll just respond to the Y and N keys.
println!("Are you ready to bid your window farewell? [Y/N]");
close_requested = true;
// In applications where you can safely close the window without further
// action from the user, this is generally where you'd handle cleanup before
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
match key.as_ref() {
Key::Character("y") => {
if close_requested {
// This is where you'll want to do any cleanup you need.
println!("Buh-bye!");
// For a single-window application like this, you'd normally just
// break out of the event loop here. If you wanted to keep running the
// event loop (i.e. if it's a multi-window application), you need to
// drop the window. That closes it, and results in `Destroyed` being
// sent.
control_flow.set_exit();
}
}
Key::Character("n") => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
}
}
_ => (),
}
}
_ => (),
// In applications where you can safely close the window without further
// action from the user, this is generally where you'd handle cleanup before
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
match key.as_ref() {
Key::Character("y") => {
if close_requested {
// This is where you'll want to do any cleanup you need.
println!("Buh-bye!");
// For a single-window application like this, you'd normally just
// break out of the event loop here. If you wanted to keep running the
// event loop (i.e. if it's a multi-window application), you need to
// drop the window. That closes it, and results in `Destroyed` being
// sent.
elwt.exit();
}
}
Key::Character("n") => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
}
}
_ => (),
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

@@ -5,7 +5,7 @@ use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, Ime, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
keyboard::{Key, KeyCode},
window::{ImePurpose, WindowBuilder},
};
@@ -39,71 +39,56 @@ fn main() -> Result<(), impl std::error::Error> {
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
cursor_position = position;
}
Event::WindowEvent {
event:
WindowEvent::MouseInput {
state: ElementState::Released,
..
},
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
}
}
Event::WindowEvent {
event: WindowEvent::Ime(event),
..
} => {
println!("{event:?}");
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
WindowEvent::MouseInput {
state: ElementState::Released,
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { event, .. },
..
} => {
println!("key: {event:?}");
WindowEvent::Ime(event) => {
println!("{event:?}");
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
WindowEvent::KeyboardInput { event, .. } => {
println!("key: {event:?}");
if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 {
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME allowed: {ime_allowed}\n");
if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 {
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME allowed: {ime_allowed}\n");
}
if event.state == ElementState::Pressed && event.logical_key == Key::F3 {
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,
_ => ImePurpose::Normal,
};
window.set_ime_purpose(ime_purpose);
println!("\nIME purpose: {ime_purpose:?}\n");
}
}
if event.state == ElementState::Pressed && event.logical_key == Key::F3 {
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,
_ => ImePurpose::Normal,
};
window.set_ime_purpose(ime_purpose);
println!("\nIME purpose: {ime_purpose:?}\n");
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

@@ -4,7 +4,7 @@
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
// WARNING: This is not available on all platforms (for example on the web).
platform::modifier_supplement::KeyEventExtModifierSupplement,
@@ -31,12 +31,10 @@ fn main() -> Result<(), impl std::error::Error> {
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
@@ -54,12 +52,11 @@ fn main() -> Result<(), impl std::error::Error> {
}
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
})
}

View File

@@ -34,12 +34,10 @@ In both cases the example window should move like the content of a scroll area i
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
println!("mouse wheel Line Delta: ({x},{y})");
@@ -57,12 +55,11 @@ In other words, the deltas indicate the direction in which to move the content (
window.set_outer_position(pos)
}
},
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

@@ -173,11 +173,10 @@ fn main() -> Result<(), impl std::error::Error> {
}
});
}
event_loop.run(move |event, _event_loop, control_flow| {
match !window_senders.is_empty() {
true => control_flow.set_wait(),
false => control_flow.set_exit(),
};
event_loop.run(move |event, elwt| {
if window_senders.is_empty() {
elwt.exit()
}
match event {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested

View File

@@ -26,45 +26,40 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Press N to open a new window.");
event_loop.run(move |event, event_loop, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
match event {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
// This drops the window, causing it to close.
windows.remove(&window_id);
// This drops the window, causing it to close.
windows.remove(&window_id);
if windows.is_empty() {
control_flow.set_exit();
}
if windows.is_empty() {
elwt.exit();
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: Key::Character(c),
..
},
is_synthetic: false,
..
} if matches!(c.as_ref(), "n" | "N") => {
let window = Window::new(event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: Key::Character(c),
..
},
is_synthetic: false,
..
} if matches!(c.as_ref(), "n" | "N") => {
let window = Window::new(elwt).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
_ => (),
}
_ => (),
}
Event::RedrawRequested(window_id) => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
})
}

View File

@@ -19,27 +19,24 @@ fn main() -> Result<(), impl std::error::Error> {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
println!("{event:?}");
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::MouseInput {
state: ElementState::Released,
..
} => {
window.request_redraw();
}
WindowEvent::RedrawRequested => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

@@ -33,17 +33,18 @@ fn main() -> Result<(), impl std::error::Error> {
}
});
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
println!("{event:?}");
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}

View File

@@ -27,12 +27,10 @@ fn main() -> Result<(), impl std::error::Error> {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
@@ -46,12 +44,11 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Resizable: {resizable}");
window.set_resizable(resizable);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
})
}

View File

@@ -32,60 +32,53 @@ mod example {
let mut counter = 0;
let mut create_first_window = false;
event_loop.run(move |event, elwt, flow| {
event_loop.run(move |event, elwt| {
match event {
Event::Resumed => create_first_window = true,
Event::WindowEvent {
window_id,
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key,
state: ElementState::Pressed,
..
},
..
},
} => {
if logical_key == Key::Character("n".into()) {
if let Some(window) = windows.get(&window_id) {
// Request a new activation token on this window.
// Once we get it we will use it to create a window.
window
.request_activation_token()
.expect("Failed to request activation token.");
Event::WindowEvent { window_id, event } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key,
state: ElementState::Pressed,
..
},
..
} => {
if logical_key == Key::Character("n".into()) {
if let Some(window) = windows.get(&window_id) {
// Request a new activation token on this window.
// Once we get it we will use it to create a window.
window
.request_activation_token()
.expect("Failed to request activation token.");
}
}
}
}
Event::WindowEvent {
window_id,
event: WindowEvent::CloseRequested,
} => {
// Remove the window from the map.
windows.remove(&window_id);
if windows.is_empty() {
flow.set_exit();
return;
WindowEvent::CloseRequested => {
// Remove the window from the map.
windows.remove(&window_id);
if windows.is_empty() {
elwt.exit();
return;
}
}
}
Event::WindowEvent {
event: WindowEvent::ActivationTokenDone { token, .. },
..
} => {
current_token = Some(token);
}
Event::RedrawRequested(id) => {
if let Some(window) = windows.get(&id) {
super::fill::fill_window(window);
WindowEvent::ActivationTokenDone { token, .. } => {
current_token = Some(token);
}
}
_ => {}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
super::fill::fill_window(window);
}
}
_ => {}
},
_ => (),
}
// See if we've passed the deadline.
@@ -109,8 +102,6 @@ mod example {
counter += 1;
create_first_window = false;
}
flow.set_wait();
})
}
}

View File

@@ -3,7 +3,7 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
keyboard::Key,
window::{Theme, WindowBuilder},
};
@@ -27,53 +27,42 @@ fn main() -> Result<(), impl std::error::Error> {
println!(" (L) Light theme");
println!(" (D) Dark theme");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::ThemeChanged(theme),
window_id,
..
} if window_id == window.id() => {
println!("Theme is changed: {theme:?}")
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
},
..
} => match key.as_ref() {
Key::Character("A" | "a") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(None);
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { window_id, event } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::ThemeChanged(theme) if window_id == window.id() => {
println!("Theme is changed: {theme:?}")
}
Key::Character("L" | "l") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Light));
}
Key::Character("D" | "d") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Dark));
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match key.as_ref() {
Key::Character("A" | "a") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(None);
}
Key::Character("L" | "l") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Light));
}
Key::Character("D" | "d") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Dark));
}
_ => (),
},
WindowEvent::RedrawRequested => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

@@ -9,7 +9,7 @@ use web_time::Instant;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
@@ -27,22 +27,25 @@ fn main() -> Result<(), impl std::error::Error> {
let timer_length = Duration::new(1, 0);
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::NewEvents(StartCause::Init) => {
control_flow.set_wait_until(Instant::now() + timer_length);
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
}
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
control_flow.set_wait_until(Instant::now() + timer_length);
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
println!("\nTimer\n");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
}
_ => (),

View File

@@ -1,7 +1,7 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -19,12 +19,10 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Only supported on macOS at the moment.");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::TouchpadMagnify { delta, .. } => {
if delta > 0.0 {
println!("Zoomed in {delta}");
@@ -42,10 +40,11 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Rotated clockwise {delta}");
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
})
}

View File

@@ -22,19 +22,17 @@ fn main() -> Result<(), impl std::error::Error> {
window.set_title("A fantastic window!");
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
_ => (),
}
})
}

View File

@@ -9,7 +9,7 @@
use winit::window::Window;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
pub(super) fn fill_window(window: &Window) {
use softbuffer::{Context, Surface};
use std::cell::RefCell;
@@ -80,7 +80,7 @@ pub(super) fn fill_window(window: &Window) {
})
}
#[cfg(any(target_os = "android", target_os = "ios"))]
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
pub(super) fn fill_window(_window: &Window) {
// No-op on mobile platforms.
}

View File

@@ -21,9 +21,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
#[cfg(wasm_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
#[cfg(wasm_platform)]
wasm::log_event(&log_list, &event);
@@ -31,7 +29,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
@@ -65,7 +63,10 @@ mod wasm {
use softbuffer::{Surface, SurfaceExtWeb};
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
use winit::{
event::{Event, WindowEvent},
window::Window,
};
#[wasm_bindgen(start)]
pub fn run() {
@@ -116,6 +117,10 @@ mod wasm {
// So we implement this basic logging system into the page to give developers an easy alternative.
// As a bonus its also kind of handy on desktop.
let event = match event {
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => None,
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
_ => None,

View File

@@ -12,7 +12,7 @@ mod wasm {
use winit::{
dpi::PhysicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
platform::web::WindowBuilderExtWebSys,
window::{Window, WindowBuilder},
};
@@ -47,18 +47,14 @@ This example demonstrates the desired future functionality which will possibly b
// Render once with the size info we currently have
render_circle(&canvas, window.inner_size());
let _ = event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::Resized(resize),
window_id,
} if window_id == window.id() => {
render_circle(&canvas, resize);
}
_ => (),
let _ = event_loop.run(move |event, _| match event {
Event::WindowEvent {
event: WindowEvent::Resized(resize),
window_id,
} if window_id == window.id() => {
render_circle(&canvas, resize);
}
_ => (),
});
}

View File

@@ -20,23 +20,23 @@ fn main() -> Result<(), impl std::error::Error> {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
}
})

View File

@@ -31,45 +31,38 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
},
..
} => match key.as_ref() {
Key::Character("F" | "f") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
}
Key::Character("G" | "g") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
}
Key::Character("H" | "h") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { window_id, event } = event {
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match key.as_ref() {
Key::Character("F" | "f") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
}
Key::Character("G" | "g") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
}
Key::Character("H" | "h") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
}
_ => (),
},
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
})
}

View File

@@ -38,9 +38,7 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
match event {
// This used to use the virtual key, but the new API
// only provides the `physical_key` (`Code`).
@@ -68,75 +66,71 @@ fn main() -> Result<(), impl std::error::Error> {
}
_ => (),
},
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(key_str),
state: ElementState::Pressed,
..
},
..
},
..
} => match key_str.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
"e" => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
Event::WindowEvent { window_id, event } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(key_str),
state: ElementState::Pressed,
..
},
..
} => match key_str.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
"e" => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
}
}
"f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
"f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
}
"p" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
"p" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
}
"m" => {
minimized = !minimized;
window.set_minimized(minimized);
}
"q" => {
control_flow.set_exit();
}
"v" => {
visible = !visible;
window.set_visible(visible);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
"m" => {
minimized = !minimized;
window.set_minimized(minimized);
}
"q" => {
elwt.exit();
}
"v" => {
visible = !visible;
window.set_visible(visible);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
_ => (),
},
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
})

View File

@@ -3,7 +3,7 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
keyboard::Key,
window::{CursorIcon, ResizeDirection, WindowBuilder},
};
@@ -27,12 +27,12 @@ fn main() -> Result<(), impl std::error::Error> {
let mut border = false;
let mut cursor_location = None;
event_loop.run(move |event, _, control_flow| match event {
event_loop.run(move |event, elwt| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Press 'B' to toggle borderless")
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => {
if !window.is_decorated() {
let new_location =
@@ -68,11 +68,12 @@ fn main() -> Result<(), impl std::error::Error> {
border = !border;
window.set_decorations(border);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
})
}

View File

@@ -4,7 +4,7 @@ use std::path::Path;
use simple_logger::SimpleLogger;
use winit::{
event::Event,
event::{Event, WindowEvent},
event_loop::EventLoop,
window::{Icon, WindowBuilder},
};
@@ -33,20 +33,16 @@ fn main() -> Result<(), impl std::error::Error> {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
use winit::event::WindowEvent::*;
match event {
CloseRequested => control_flow.set_exit(),
DroppedFile(path) => {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::DroppedFile(path) => {
window.set_window_icon(Some(load_icon(&path)));
}
WindowEvent::RedrawRequested => fill::fill_window(&window),
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
})
}

View File

@@ -11,7 +11,7 @@ fn main() -> Result<(), impl std::error::Error> {
error::EventLoopError,
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_ondemand::EventLoopExtRunOnDemand,
platform::run_on_demand::EventLoopExtRunOnDemand,
window::{Window, WindowBuilder, WindowId},
};
@@ -30,8 +30,7 @@ fn main() -> Result<(), impl std::error::Error> {
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
let mut app = App::default();
event_loop.run_ondemand(move |event, event_loop, control_flow| {
control_flow.set_wait();
event_loop.run_on_demand(move |event, elwt| {
println!("Run {idx}: {:?}", event);
if let Some(window) = &app.window {
@@ -44,7 +43,10 @@ fn main() -> Result<(), impl std::error::Error> {
app.window = None;
}
Event::AboutToWait => window.request_redraw(),
Event::RedrawRequested(_) => {
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(window);
}
_ => (),
@@ -57,7 +59,7 @@ fn main() -> Result<(), impl std::error::Error> {
} if id == window_id => {
println!("--------------------------------------------------------- Window {idx} Destroyed");
app.window_id = None;
control_flow.set_exit();
elwt.exit();
}
_ => (),
}
@@ -65,7 +67,7 @@ fn main() -> Result<(), impl std::error::Error> {
let window = WindowBuilder::new()
.with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(event_loop)
.build(elwt)
.unwrap();
app.window_id = Some(window.id());
app.window = Some(window);

View File

@@ -31,41 +31,38 @@ fn main() -> Result<(), impl std::error::Error> {
let mut option_as_alt = window.option_as_alt();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
option_as_alt = match option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
option_as_alt = match option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
window.set_option_as_alt(option_as_alt);
}
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
window.set_option_as_alt(option_as_alt);
}
Event::RedrawRequested(_) => {
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => (),
})
}

View File

@@ -14,7 +14,7 @@ fn main() -> std::process::ExitCode {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::WindowBuilder,
};
@@ -32,9 +32,7 @@ fn main() -> std::process::ExitCode {
'main: loop {
let timeout = Some(Duration::ZERO);
let status = event_loop.pump_events(timeout, |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
let status = event_loop.pump_events(timeout, |event, elwt| {
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{event:?}");
@@ -44,11 +42,14 @@ fn main() -> std::process::ExitCode {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
}
_ => (),

View File

@@ -24,27 +24,18 @@ fn main() -> Result<(), impl std::error::Error> {
let mut has_increments = true;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent {
event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Space,
state: ElementState::Released,
..
},
KeyEvent {
logical_key: Key::Space,
state: ElementState::Released,
..
},
window_id,
} if window_id == window.id() => {
..
} => {
has_increments = !has_increments;
let new_increments = match window.resize_increments() {
@@ -54,11 +45,13 @@ fn main() -> Result<(), impl std::error::Error> {
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Event::AboutToWait => window.request_redraw(),
Event::RedrawRequested(_) => {
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
},
Event::AboutToWait => window.request_redraw(),
_ => (),
})
}

View File

@@ -30,78 +30,73 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Press N to open a new window.");
event_loop.run(move |event, event_loop, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
match event {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
// This drops the window, causing it to close.
windows.remove(&window_id);
// This drops the window, causing it to close.
windows.remove(&window_id);
if windows.is_empty() {
control_flow.set_exit();
}
if windows.is_empty() {
elwt.exit();
}
WindowEvent::Resized(_) => {
if let Some(window) = windows.get(&window_id) {
window.request_redraw();
}
}
WindowEvent::Resized(_) => {
if let Some(window) = windows.get(&window_id) {
window.request_redraw();
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key,
..
},
is_synthetic: false,
..
} => match logical_key.as_ref() {
Key::Character("t") => {
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
let window = WindowBuilder::new()
.with_tabbing_identifier(&tabbing_id)
.build(event_loop)
.unwrap();
println!("Added a new tab: {:?}", window.id());
windows.insert(window.id(), window);
}
Key::Character("w") => {
let _ = windows.remove(&window_id);
}
Key::ArrowRight => {
windows.get(&window_id).unwrap().select_next_tab();
}
Key::ArrowLeft => {
windows.get(&window_id).unwrap().select_previous_tab();
}
Key::Character(ch) => {
if let Ok(index) = ch.parse::<NonZeroUsize>() {
let index = index.get();
// Select the last tab when pressing `9`.
let window = windows.get(&window_id).unwrap();
if index == 9 {
window.select_tab_at_index(window.num_tabs() - 1)
} else {
window.select_tab_at_index(index - 1);
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key,
..
},
is_synthetic: false,
..
} => match logical_key.as_ref() {
Key::Character("t") => {
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
let window = WindowBuilder::new()
.with_tabbing_identifier(&tabbing_id)
.build(elwt)
.unwrap();
println!("Added a new tab: {:?}", window.id());
windows.insert(window.id(), window);
}
Key::Character("w") => {
let _ = windows.remove(&window_id);
}
Key::ArrowRight => {
windows.get(&window_id).unwrap().select_next_tab();
}
Key::ArrowLeft => {
windows.get(&window_id).unwrap().select_previous_tab();
}
Key::Character(ch) => {
if let Ok(index) = ch.parse::<NonZeroUsize>() {
let index = index.get();
// Select the last tab when pressing `9`.
let window = windows.get(&window_id).unwrap();
if index == 9 {
window.select_tab_at_index(window.num_tabs() - 1)
} else {
window.select_tab_at_index(index - 1);
}
}
_ => (),
},
}
_ => (),
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
Event::RedrawRequested(window_id) => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
})
}

View File

@@ -32,18 +32,19 @@ mod imple {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);

View File

@@ -82,8 +82,7 @@
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
//! integers (most often 1 or 2).
//! - **Wayland:** Scale factor is suggested by the the compositor.
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.

View File

@@ -7,25 +7,24 @@
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
//!
//! ```rust,ignore
//! let mut control_flow = ControlFlow::Poll;
//! let mut start_cause = StartCause::Init;
//!
//! while control_flow != ControlFlow::Exit {
//! event_handler(NewEvents(start_cause), ..., &mut control_flow);
//! while !elwt.exiting() {
//! event_handler(NewEvents(start_cause), elwt);
//!
//! for e in (window events, user events, device events) {
//! event_handler(e, ..., &mut control_flow);
//! event_handler(e, elwt);
//! }
//!
//! for w in (redraw windows) {
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
//! event_handler(RedrawRequested(w), elwt);
//! }
//!
//! event_handler(AboutToWait, ..., &mut control_flow);
//! start_cause = wait_if_necessary(control_flow);
//! event_handler(AboutToWait, elwt);
//! start_cause = wait_if_necessary();
//! }
//!
//! event_handler(LoopExiting, ..., &mut control_flow);
//! event_handler(LoopExiting, elwt);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
@@ -62,7 +61,7 @@ pub enum Event<T: 'static> {
///
/// This event type is useful as a place to put code that should be done before you start
/// processing events, such as updating frame timing information for benchmarking or checking
/// the [`StartCause`][crate::event::StartCause] to see if a timer set by
/// the [`StartCause`] to see if a timer set by
/// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed.
NewEvents(StartCause),
@@ -218,26 +217,40 @@ pub enum Event<T: 'static> {
/// ups and also lots of corresponding `AboutToWait` events.
///
/// This is not an ideal event to drive application rendering from and instead applications
/// should render in response to [`Event::RedrawRequested`](crate::event::Event::RedrawRequested)
/// events.
/// should render in response to [`WindowEvent::RedrawRequested`] events.
AboutToWait,
/// Emitted when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
///
/// Winit will aggregate duplicate redraw requests into a single event, to
/// help avoid duplicating rendering work.
RedrawRequested(WindowId),
/// Emitted when the event loop is being shut down.
///
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
/// gets emitted. You generally want to treat this as a "do on quit" event.
LoopExiting,
/// Emitted when the application has received a memory warning.
///
/// ## Platform-specific
///
/// ### Android
///
/// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application
/// must [release memory] or risk being killed.
///
/// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory()
/// [release memory]: https://developer.android.com/topic/performance/memory#release
///
/// ### iOS
///
/// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`]
/// callback. The application must free as much memory as possible or risk being terminated, see
/// [how to respond to memory warnings].
///
/// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni
/// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings
///
/// ### Others
///
/// - **macOS / Wayland / Windows / Orbital:** Unsupported.
MemoryWarning,
}
impl<T> Event<T> {
@@ -250,10 +263,10 @@ impl<T> Event<T> {
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
AboutToWait => Ok(AboutToWait),
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
MemoryWarning => Ok(MemoryWarning),
}
}
}
@@ -545,15 +558,39 @@ pub enum WindowEvent {
/// This is different to window visibility as it depends on whether the window is closed,
/// minimised, set invisible, or fully occluded by another window.
///
/// Platform-specific behavior:
/// ## Platform-specific
///
/// ### iOS
///
/// On iOS, the `Occluded(false)` event is emitted in response to an [`applicationWillEnterForeground`]
/// callback which means the application should start preparing its data. The `Occluded(true)` event is
/// emitted in response to an [`applicationDidEnterBackground`] callback which means the application
/// should free resources (according to the [iOS application lifecycle]).
///
/// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground
/// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
///
/// ### Others
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **iOS / Android / Wayland / Windows / Orbital:** Unsupported.
/// - **Android / Windows / Orbital:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
Occluded(bool),
/// Emitted when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
///
/// Winit will aggregate duplicate redraw requests into a single event, to
/// help avoid duplicating rendering work.
RedrawRequested,
}
/// Identifier of an input device.
@@ -575,7 +612,8 @@ impl DeviceId {
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
}
}
@@ -621,10 +659,6 @@ pub enum DeviceEvent {
},
Key(RawKeyEvent),
Text {
codepoint: char,
},
}
/// Describes a keyboard input as a raw device event.
@@ -674,7 +708,7 @@ pub struct KeyEvent {
// Allowing `broken_intra_doc_links` for `logical_key`, because
// `key_without_modifiers` is not available on all platforms
#[cfg_attr(
not(any(target_os = "macos", target_os = "windows", target_os = "linux")),
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links)
)]
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
@@ -942,7 +976,10 @@ pub struct Touch {
///
/// ## Platform-specific
///
/// - Only available on **iOS** 9.0+, **Windows** 8+, and **Web**.
/// - 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).
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
@@ -1032,7 +1069,7 @@ impl ElementState {
///
/// **macOS:** `Back` and `Forward` might not work with all hardware.
/// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
@@ -1123,7 +1160,6 @@ mod tests {
let wid = unsafe { WindowId::dummy() };
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(RedrawRequested(wid));
x(AboutToWait);
x(LoopExiting);
x(Suspended);
@@ -1222,7 +1258,6 @@ mod tests {
button: 0,
state: event::ElementState::Pressed,
});
with_device_event(Text { codepoint: 'a' });
}
}};
}

View File

@@ -12,7 +12,6 @@ use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{error, fmt};
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
#[cfg(not(wasm_platform))]
use std::time::{Duration, Instant};
#[cfg(wasm_platform)]
@@ -147,28 +146,21 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
}
}
/// Set by the user callback given to the [`EventLoop::run`] method.
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Poll`].
/// Defaults to [`Wait`].
///
/// ## Persistency
///
/// Almost every change is persistent between multiple calls to the event loop closure within a
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
/// Changes are **not** persistent between multiple calls to `run_ondemand` - issuing a new call will
/// reset the control flow to [`Poll`].
///
/// [`ExitWithCode`]: Self::ExitWithCode
/// [`Poll`]: Self::Poll
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// [`Wait`]: Self::Wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
#[default]
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
@@ -180,88 +172,22 @@ pub enum ControlFlow {
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
/// Send a [`LoopExiting`] event and stop the event loop. This variant is *sticky* - once set,
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
///
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
/// with exit code 0.
///
/// ## Platform-specific
///
/// - **Android / iOS / Web:** The supplied exit code is unused.
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
/// [`std::process::exit`].
///
/// [`LoopExiting`]: Event::LoopExiting
/// [`Exit`]: ControlFlow::Exit
ExitWithCode(i32),
}
impl ControlFlow {
/// Alias for [`ExitWithCode`]`(0)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
#[allow(non_upper_case_globals)]
pub const Exit: Self = Self::ExitWithCode(0);
/// Sets this to [`Poll`].
///
/// [`Poll`]: Self::Poll
pub fn set_poll(&mut self) {
*self = Self::Poll;
}
/// Sets this to [`Wait`].
///
/// [`Wait`]: Self::Wait
pub fn set_wait(&mut self) {
*self = Self::Wait;
}
/// Sets this to [`WaitUntil`]`(instant)`.
///
/// [`WaitUntil`]: Self::WaitUntil
pub fn set_wait_until(&mut self, instant: Instant) {
*self = Self::WaitUntil(instant);
}
/// Sets this to wait until a timeout has expired.
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn set_wait_timeout(&mut self, timeout: Duration) {
pub fn wait_duration(timeout: Duration) -> Self {
match Instant::now().checked_add(timeout) {
Some(instant) => self.set_wait_until(instant),
None => self.set_wait(),
Some(instant) => Self::WaitUntil(instant),
None => Self::Wait,
}
}
/// Sets this to [`ExitWithCode`]`(code)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
pub fn set_exit_with_code(&mut self, code: i32) {
*self = Self::ExitWithCode(code);
}
/// Sets this to [`Exit`].
///
/// [`Exit`]: Self::Exit
pub fn set_exit(&mut self) {
*self = Self::Exit;
}
}
impl Default for ControlFlow {
#[inline(always)]
fn default() -> Self {
Self::Poll
}
}
impl EventLoop<()> {
@@ -286,13 +212,10 @@ impl<T> EventLoop<T> {
/// Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
///
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
/// event loop's behavior.
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
/// disconnects.
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception
@@ -306,12 +229,12 @@ impl<T> EventLoop<T> {
///
/// This function won't be available with `target_feature = "exception-handling"`.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
#[inline]
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &EventLoopWindowTarget<T>),
{
self.event_loop.run(event_handler)
}
@@ -324,10 +247,18 @@ impl<T> EventLoop<T> {
}
}
unsafe impl<T> HasRawDisplayHandle for EventLoop<T> {
#[cfg(feature = "rwh_06")]
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)
}
}
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.event_loop.window_target().p.raw_display_handle()
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(&**self)
}
}
@@ -355,7 +286,7 @@ impl<T> EventLoopWindowTarget<T> {
///
/// ## Platform-specific
///
/// **Wayland:** Always returns `None`.
/// **Wayland / Web:** Always returns `None`.
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.p
@@ -374,16 +305,49 @@ impl<T> EventLoopWindowTarget<T> {
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
#[cfg(any(x11_platform, wasm_platform, wayland_platform, windows))]
self.p.listen_device_events(_allowed);
pub fn listen_device_events(&self, allowed: DeviceEvents) {
self.p.listen_device_events(allowed);
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
}
/// Gets the current [`ControlFlow`].
pub fn control_flow(&self) -> ControlFlow {
self.p.control_flow()
}
/// This exits the event loop.
///
/// See [`LoopExiting`](Event::LoopExiting).
pub fn exit(&self) {
self.p.exit()
}
/// Returns if the [`EventLoop`] is about to stop.
///
/// See [`exit()`](Self::exit).
pub fn exiting(&self) -> bool {
self.p.exiting()
}
}
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
#[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.p.raw_display_handle_rwh_06()?;
// SAFETY: The display will never be deallocated while the event loop is alive.
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) })
}
}
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.p.raw_display_handle()
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05()
}
}

View File

@@ -82,7 +82,7 @@ pub use smol_str::SmolStr;
///
/// - Correctly match key press and release events.
/// - On non-web platforms, support assigning keybinds to virtually any key through a UI.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKeyCode {
Unidentified,
@@ -196,7 +196,7 @@ impl std::fmt::Debug for NativeKey {
///
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyCode {
/// This variant is used when the key cannot be translated to any other variant.

View File

@@ -30,9 +30,7 @@
//!
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
//! point [`Event`]`::`[`LoopExiting`] is emitted and the entire program terminates.
//! 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 poorly on
@@ -44,30 +42,30 @@
//! ```no_run
//! use winit::{
//! event::{Event, WindowEvent},
//! event_loop::EventLoop,
//! event_loop::{ControlFlow, EventLoop},
//! window::WindowBuilder,
//! };
//!
//! let event_loop = EventLoop::new().unwrap();
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
//!
//! event_loop.run(move |event, _, control_flow| {
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! control_flow.set_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.
//! control_flow.set_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);
//!
//! event_loop.run(move |event, elwt| {
//! match event {
//! Event::WindowEvent {
//! event: WindowEvent::CloseRequested,
//! ..
//! } => {
//! println!("The close button was pressed; stopping");
//! control_flow.set_exit();
//! elwt.exit();
//! },
//! Event::AboutToWait => {
//! // Application update code.
@@ -79,7 +77,10 @@
//! // can just render here instead.
//! window.request_redraw();
//! },
//! Event::RedrawRequested(_) => {
//! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested,
//! ..
//! } => {
//! // Redraw the application.
//! //
//! // It's preferable for applications that do not render continuously to render in
@@ -112,9 +113,7 @@
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder
@@ -134,11 +133,15 @@
#![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::missing_safety_doc)]
#[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle;
#[allow(unused_imports)]
#[macro_use]
extern crate log;

View File

@@ -108,20 +108,12 @@ impl MonitorHandle {
/// Returns a human-readable name of the monitor.
///
/// Returns `None` if the monitor doesn't exist anymore.
///
/// ## Platform-specific
///
/// - **Web:** Always returns None
#[inline]
pub fn name(&self) -> Option<String> {
self.inner.name()
}
/// Returns the monitor's resolution.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.inner.size()
@@ -129,10 +121,6 @@ impl MonitorHandle {
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
self.inner.position()
@@ -158,7 +146,6 @@ impl MonitorHandle {
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns 1.0
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()

View File

@@ -1,5 +1,6 @@
use std::os::raw::c_void;
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use crate::{
@@ -117,7 +118,7 @@ pub trait WindowBuilderExtIOS {
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder;
fn with_scale_factor(self, scale_factor: f64) -> Self;
/// Sets the valid orientations for the [`Window`].
///
@@ -125,7 +126,7 @@ pub trait WindowBuilderExtIOS {
///
/// This sets the initial value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
/// Sets whether the [`Window`] prefers the home indicator hidden.
///
@@ -135,7 +136,7 @@ pub trait WindowBuilderExtIOS {
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
/// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling.
@@ -144,10 +145,7 @@ pub trait WindowBuilderExtIOS {
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_preferred_screen_edges_deferring_system_gestures(
self,
edges: ScreenEdge,
) -> WindowBuilder;
fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
/// Sets whether the [`Window`] prefers the status bar hidden.
///
@@ -155,40 +153,37 @@ pub trait WindowBuilderExtIOS {
///
/// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
}
impl WindowBuilderExtIOS for WindowBuilder {
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.platform_specific.scale_factor = Some(scale_factor);
self
}
#[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
self.platform_specific.valid_orientations = valid_orientations;
self
}
#[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_home_indicator_hidden = hidden;
self
}
#[inline]
fn with_preferred_screen_edges_deferring_system_gestures(
mut self,
edges: ScreenEdge,
) -> WindowBuilder {
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
self.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges;
self
}
#[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_status_bar_hidden = hidden;
self
}
@@ -210,7 +205,9 @@ pub trait MonitorHandleExtIOS {
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
Id::as_ptr(self.inner.ui_screen()) as *mut c_void
// SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
}
#[inline]

View File

@@ -183,92 +183,88 @@ pub enum ActivationPolicy {
/// - `with_fullsize_content_view`
pub trait WindowBuilderExtMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool)
-> WindowBuilder;
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
/// Makes the titlebar transparent and allows the content to appear behind it.
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
/// Hides the window title.
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
fn with_title_hidden(self, title_hidden: bool) -> Self;
/// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
/// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
fn with_has_shadow(self, has_shadow: bool) -> Self;
/// Window accepts click-through mouse events.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
/// Defines the window tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn with_tabbing_identifier(self, identifier: &str) -> WindowBuilder;
fn with_tabbing_identifier(self, identifier: &str) -> Self;
/// Set how the <kbd>Option</kbd> keys are interpreted.
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
}
impl WindowBuilderExtMacOS for WindowBuilder {
#[inline]
fn with_movable_by_window_background(
mut self,
movable_by_window_background: bool,
) -> WindowBuilder {
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background;
self
}
#[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.platform_specific.titlebar_transparent = titlebar_transparent;
self
}
#[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.platform_specific.titlebar_hidden = titlebar_hidden;
self
}
#[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
#[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.platform_specific.title_hidden = title_hidden;
self
}
#[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.platform_specific.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.platform_specific.has_shadow = has_shadow;
self
}
#[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> WindowBuilder {
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self
}
#[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> WindowBuilder {
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific
.tabbing_identifier
.replace(tabbing_identifier.to_string());
@@ -276,7 +272,7 @@ impl WindowBuilderExtMacOS for WindowBuilder {
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.platform_specific.option_as_alt = option_as_alt;
self
}

View File

@@ -11,7 +11,7 @@
//!
//! And the following platform-specific modules:
//!
//! - `run_ondemand` (available on `windows`, `unix`, `macos`, `android`)
//! - `run_on_demand` (available on `windows`, `unix`, `macos`, `android`)
//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`)
//!
//! However only the module corresponding to the platform you're compiling to will be available.
@@ -42,7 +42,7 @@ pub mod x11;
x11_platform,
wayland_platform
))]
pub mod run_ondemand;
pub mod run_on_demand;
#[cfg(any(
windows_platform,

View File

@@ -2,7 +2,7 @@ use std::time::Duration;
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
event_loop::{EventLoop, EventLoopWindowTarget},
};
/// The return status for `pump_events`
@@ -63,7 +63,7 @@ pub trait EventLoopExtPumpEvents {
///
/// 'main: loop {
/// let timeout = Some(Duration::ZERO);
/// let status = event_loop.pump_events(timeout, |event, _, control_flow| {
/// let status = event_loop.pump_events(timeout, |event, elwt| {
/// # if let Event::WindowEvent { event, .. } = &event {
/// # // Print only Window events to reduce noise
/// # println!("{event:?}");
@@ -73,7 +73,7 @@ pub trait EventLoopExtPumpEvents {
/// Event::WindowEvent {
/// event: WindowEvent::CloseRequested,
/// window_id,
/// } if window_id == window.id() => control_flow.set_exit(),
/// } if window_id == window.id() => elwt.exit(),
/// Event::AboutToWait => {
/// window.request_redraw();
/// }
@@ -174,7 +174,7 @@ pub trait EventLoopExtPumpEvents {
/// callback.
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
@@ -182,7 +182,7 @@ impl<T> EventLoopExtPumpEvents for EventLoop<T> {
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.pump_events(timeout, event_handler)
}

View File

@@ -1,7 +1,7 @@
use crate::{
error::EventLoopError,
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
event_loop::{EventLoop, EventLoopWindowTarget},
};
#[cfg(doc)]
@@ -17,7 +17,7 @@ pub trait EventLoopExtRunOnDemand {
///
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
/// and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by setting the `control_flow` to [`ControlFlow::Exit`]) and
/// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit.
///
/// It's expected that each run of the loop will be for orthogonal instantiations of your
@@ -28,12 +28,11 @@ pub trait EventLoopExtRunOnDemand {
/// to while maintaining the full state of your application. (If you need something like this
/// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API)
///
/// Each time `run_ondemand` is called the `event_handler` can expect to receive a
/// Each time `run_on_demand` is called the `event_handler` can expect to receive a
/// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume
/// lifecycle) - which can be used to consistently initialize application state.
///
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
/// event loop's behavior.
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to
@@ -57,18 +56,21 @@ pub trait EventLoopExtRunOnDemand {
/// polled to ask for 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.
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
///
/// [`exit()`]: EventLoopWindowTarget::exit
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.run_ondemand(event_handler)
self.event_loop.run_on_demand(event_handler)
}
}

View File

@@ -85,12 +85,7 @@ impl WindowBuilderExtStartupNotify for WindowBuilder {
///
/// This is wise to do before running child processes,
/// which may not to support the activation token.
///
/// # Safety
///
/// While the function is safe internally, it mutates the global environment
/// state for the process, hence unsafe.
pub unsafe fn reset_activation_token_env() {
pub fn reset_activation_token_env() {
env::remove_var(X11_VAR);
env::remove_var(WAYLAND_VAR);
}
@@ -98,12 +93,7 @@ pub unsafe fn reset_activation_token_env() {
/// Set environment variables responsible for activation token.
///
/// This could be used before running daemon processes.
///
/// # Safety
///
/// While the function is safe internally, it mutates the global environment
/// state for the process, hence unsafe.
pub unsafe fn set_activation_token_env(token: ActivationToken) {
pub fn set_activation_token_env(token: ActivationToken) {
env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token._token);
}

View File

@@ -28,7 +28,6 @@
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
use crate::event::Event;
use crate::event_loop::ControlFlow;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::window::{Window, WindowBuilder};
@@ -48,8 +47,8 @@ impl WindowExtWebSys for Window {
}
pub trait WindowBuilderExtWebSys {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`](crate::window::Window). If
/// [`None`], [`WindowBuilder::build()`] will create one.
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
/// [`WindowBuilder::build()`] will create one.
///
/// In any case, the canvas won't be automatically inserted into the web page.
///
@@ -122,8 +121,7 @@ pub trait EventLoopExtWebSys {
/// event loop when switching between tabs on a single page application.
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
@@ -131,9 +129,62 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.spawn(event_handler)
}
}
pub trait EventLoopWindowTargetExtWebSys {
/// Sets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn set_poll_strategy(&self, strategy: PollStrategy);
/// Gets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
}
impl<T> EventLoopWindowTargetExtWebSys for EventLoopWindowTarget<T> {
#[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) {
self.p.set_poll_strategy(strategy);
}
#[inline]
fn poll_strategy(&self) -> PollStrategy {
self.p.poll_strategy()
}
}
/// Strategy used for [`ControlFlow::Poll`](crate::event_loop::ControlFlow::Poll).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PollStrategy {
/// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`].
///
/// This strategy will wait for the browser to enter an idle period before running and might
/// be affected by browser throttling.
///
/// [`Window.requestIdleCallback()`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
IdleCallback,
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`].
///
/// This strategy will run as fast as possible without disturbing users from interacting with
/// the page and is not affected by browser throttling.
///
/// This is the default strategy.
///
/// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
#[default]
Scheduler,
}

View File

@@ -161,6 +161,7 @@ impl WindowExtWindows for Window {
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowBuilderExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`.
@@ -173,7 +174,7 @@ pub trait WindowBuilderExtWindows {
/// - An owned window is hidden when its owner is minimized.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
fn with_owner_window(self, parent: HWND) -> Self;
/// Sets a menu on the window to be created.
///
@@ -185,13 +186,13 @@ pub trait WindowBuilderExtWindows {
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
///
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
fn with_menu(self, menu: HMENU) -> WindowBuilder;
fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> Self;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
fn with_no_redirection_bitmap(self, flag: bool) -> Self;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
@@ -199,66 +200,66 @@ pub trait WindowBuilderExtWindows {
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
fn with_drag_and_drop(self, flag: bool) -> Self;
/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
fn with_skip_taskbar(self, skip: bool) -> Self;
/// Customize the window class name.
fn with_class_name<S: Into<String>>(self, class_name: S) -> WindowBuilder;
fn with_class_name<S: Into<String>>(self, class_name: S) -> Self;
/// Shows or hides the background drop shadow for undecorated windows.
///
/// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder;
fn with_undecorated_shadow(self, shadow: bool) -> Self;
}
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
fn with_owner_window(mut self, parent: HWND) -> Self {
self.platform_specific.owner = Some(parent);
self
}
#[inline]
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
fn with_menu(mut self, menu: HMENU) -> Self {
self.platform_specific.menu = Some(menu);
self
}
#[inline]
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
self.platform_specific.taskbar_icon = taskbar_icon;
self
}
#[inline]
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
self.platform_specific.no_redirection_bitmap = flag;
self
}
#[inline]
fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder {
fn with_drag_and_drop(mut self, flag: bool) -> Self {
self.platform_specific.drag_and_drop = flag;
self
}
#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
fn with_skip_taskbar(mut self, skip: bool) -> Self {
self.platform_specific.skip_taskbar = skip;
self
}
#[inline]
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> WindowBuilder {
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
self.platform_specific.class_name = class_name.into();
self
}
#[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
self.platform_specific.decoration_shadow = shadow;
self
}

View File

@@ -226,9 +226,7 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
let native = NativeKey::Android(keycode.into());
match key_char {
Some(KeyMapChar::Unicode(c)) => {
Key::Character(smol_str::SmolStr::from_iter([c].into_iter()))
}
Some(KeyMapChar::Unicode(c)) => Key::Character(smol_str::SmolStr::from_iter([c])),
Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)),
None | Some(KeyMapChar::None) => match keycode {
// Using `BrowserHome` instead of `GoHome` according to

View File

@@ -1,6 +1,7 @@
#![cfg(android_platform)]
use std::{
cell::Cell,
collections::VecDeque,
hash::Hash,
sync::{
@@ -15,15 +16,12 @@ use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
};
use once_cell::sync::Lazy;
use raw_window_handle::{
AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, InnerSizeWriter, StartCause},
event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW},
event::{self, Force, InnerSizeWriter, StartCause},
event_loop::{self, ControlFlow, DeviceEvents, EventLoopWindowTarget as RootELW},
platform::pump_events::PumpStatus,
window::{
self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel,
@@ -147,7 +145,6 @@ pub struct EventLoop<T: 'static> {
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool,
pending_redraw: bool,
control_flow: ControlFlow,
cause: StartCause,
ignore_volume_keys: bool,
combining_accent: Option<char>,
@@ -168,23 +165,6 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
}
fn sticky_exit_callback<T, F>(
evt: event::Event<T>,
target: &RootELW<T>,
control_flow: &mut ControlFlow,
callback: &mut F,
) where
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
if let ControlFlow::ExitWithCode(code) = *control_flow {
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
} else {
callback(evt, target, control_flow)
}
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
@@ -199,6 +179,8 @@ impl<T: 'static> EventLoop<T> {
window_target: event_loop::EventLoopWindowTarget {
p: EventLoopWindowTarget {
app: android_app.clone(),
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(false),
redraw_requester: RedrawRequester::new(
&redraw_flag,
android_app.create_waker(),
@@ -213,7 +195,6 @@ impl<T: 'static> EventLoop<T> {
loop_running: false,
running: false,
pending_redraw: false,
control_flow: Default::default(),
cause: StartCause::Init,
ignore_volume_keys: attributes.ignore_volume_keys,
combining_accent: None,
@@ -222,41 +203,25 @@ impl<T: 'static> EventLoop<T> {
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &RootELW<T>),
{
trace!("Mainloop iteration");
let cause = self.cause;
let mut control_flow = self.control_flow;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
sticky_exit_callback(
event::Event::NewEvents(cause),
self.window_target(),
&mut control_flow,
callback,
);
callback(event::Event::NewEvents(cause), self.window_target());
if let Some(event) = main_event {
trace!("Handling main event {:?}", event);
match event {
MainEvent::InitWindow { .. } => {
sticky_exit_callback(
event::Event::Resumed,
self.window_target(),
&mut control_flow,
callback,
);
callback(event::Event::Resumed, self.window_target());
}
MainEvent::TerminateWindow { .. } => {
sticky_exit_callback(
event::Event::Suspended,
self.window_target(),
&mut control_flow,
callback,
);
callback(event::Event::Suspended, self.window_target());
}
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
@@ -265,26 +230,22 @@ impl<T: 'static> EventLoop<T> {
}
MainEvent::GainedFocus => {
*HAS_FOCUS.write().unwrap() = true;
sticky_exit_callback(
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(true),
},
self.window_target(),
&mut control_flow,
callback,
);
}
MainEvent::LostFocus => {
*HAS_FOCUS.write().unwrap() = false;
sticky_exit_callback(
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(false),
},
self.window_target(),
&mut control_flow,
callback,
);
}
MainEvent::ConfigChanged { .. } => {
@@ -304,19 +265,11 @@ impl<T: 'static> EventLoop<T> {
scale_factor,
},
};
sticky_exit_callback(
event,
self.window_target(),
&mut control_flow,
callback,
);
callback(event, self.window_target());
}
}
MainEvent::LowMemory => {
// XXX: how to forward this state to applications?
// It seems like ideally winit should support lifecycle and
// low-memory events, especially for mobile platforms.
warn!("TODO: handle Android LowMemory notification");
callback(event::Event::MemoryWarning, self.window_target());
}
MainEvent::Start => {
// XXX: how to forward this state to applications?
@@ -363,9 +316,8 @@ impl<T: 'static> EventLoop<T> {
// Process input events
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, &mut control_flow, callback)
});
let read_event =
input_iter.next(|event| self.handle_input_event(&android_app, event, callback));
if !read_event {
break;
@@ -379,12 +331,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_events_receiver.try_recv() {
sticky_exit_callback(
crate::event::Event::UserEvent(event),
self.window_target(),
&mut control_flow,
callback,
);
callback(crate::event::Event::UserEvent(event), self.window_target());
}
}
@@ -401,26 +348,23 @@ impl<T: 'static> EventLoop<T> {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Resized(size),
};
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
callback(event, self.window_target());
}
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
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
sticky_exit_callback(
event::Event::AboutToWait,
self.window_target(),
&mut control_flow,
callback,
);
callback(event::Event::AboutToWait, self.window_target());
self.control_flow = control_flow;
self.pending_redraw = pending_redraw;
}
@@ -428,17 +372,16 @@ impl<T: 'static> EventLoop<T> {
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
control_flow: &mut ControlFlow,
callback: &mut F,
) -> InputStatus
where
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &RootELW<T>),
{
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
let window_id = window::WindowId(WindowId);
let device_id = event::DeviceId(DeviceId);
let device_id = event::DeviceId(DeviceId(motion_event.device_id()));
let phase = match motion_event.action() {
MotionAction::Down | MotionAction::PointerDown => {
@@ -477,10 +420,10 @@ impl<T: 'static> EventLoop<T> {
phase,
location,
id: pointer.pointer_id() as u64,
force: None,
force: Some(Force::Normalized(pointer.pressure() as f64)),
}),
};
sticky_exit_callback(event, self.window_target(), control_flow, callback);
callback(event, self.window_target());
}
}
}
@@ -510,7 +453,7 @@ impl<T: 'static> EventLoop<T> {
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_keycode(keycode),
@@ -523,7 +466,7 @@ impl<T: 'static> EventLoop<T> {
is_synthetic: false,
},
};
sticky_exit_callback(event, self.window_target(), control_flow, callback);
callback(event, self.window_target());
}
}
}
@@ -537,14 +480,14 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
{
self.run_ondemand(event_handler)
self.run_on_demand(event_handler)
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>),
{
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
@@ -567,7 +510,7 @@ impl<T: 'static> EventLoop<T> {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &RootELW<T>),
{
if !self.loop_running {
self.loop_running = true;
@@ -577,7 +520,6 @@ impl<T: 'static> EventLoop<T> {
// than once
self.pending_redraw = false;
self.cause = StartCause::Init;
self.control_flow = ControlFlow::Poll;
// run the initial loop iteration
self.single_iteration(None, &mut callback);
@@ -585,21 +527,15 @@ impl<T: 'static> EventLoop<T> {
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
}
if let ControlFlow::ExitWithCode(code) = self.control_flow {
if self.exiting() {
self.loop_running = false;
let mut dummy = self.control_flow;
sticky_exit_callback(
event::Event::LoopExiting,
self.window_target(),
&mut dummy,
&mut callback,
);
callback(event::Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
}
@@ -607,7 +543,7 @@ impl<T: 'static> EventLoop<T> {
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &RootELW<T>),
{
let start = Instant::now();
@@ -618,14 +554,12 @@ impl<T: 'static> EventLoop<T> {
// 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 {
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))
}
// `ExitWithCode()` will be reset to `Poll` before polling
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
min_timeout(control_flow_timeout, timeout)
@@ -660,7 +594,7 @@ impl<T: 'static> EventLoop<T> {
}
}
self.cause = match self.control_flow {
self.cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
@@ -679,8 +613,6 @@ impl<T: 'static> EventLoop<T> {
}
}
}
// `ExitWithCode()` will be reset to `Poll` before polling
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
self.single_iteration(main_event, &mut callback);
@@ -697,6 +629,14 @@ impl<T: 'static> EventLoop<T> {
waker: self.android_app.create_waker(),
}
}
fn control_flow(&self) -> ControlFlow {
self.window_target.p.control_flow()
}
fn exiting(&self) -> bool {
self.window_target.p.exiting()
}
}
pub struct EventLoopProxy<T: 'static> {
@@ -725,6 +665,8 @@ impl<T> EventLoopProxy<T> {
pub struct EventLoopWindowTarget<T: 'static> {
app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
_marker: std::marker::PhantomData<T>,
}
@@ -740,8 +682,39 @@ impl<T: 'static> EventLoopWindowTarget<T> {
v
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android(
rwh_06::AndroidDisplayHandle::new(),
))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(true)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get()
}
}
@@ -767,11 +740,11 @@ impl From<u64> for WindowId {
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DeviceId;
pub struct DeviceId(i32);
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId
DeviceId(0)
}
}
@@ -871,6 +844,8 @@ impl Window {
pub fn set_transparent(&self, _transparent: bool) {}
pub fn set_blur(&self, _blur: bool) {}
pub fn set_visible(&self, _visibility: bool) {}
pub fn is_visible(&self) -> Option<bool> {
@@ -960,13 +935,30 @@ impl Window {
))
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
#[cfg(feature = "rwh_04")]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
if let Some(native_window) = self.app.native_window().as_ref() {
let mut handle = rwh_04::AndroidNdkHandle::empty();
handle.a_native_window = native_window.ptr().as_ptr() as *mut _;
rwh_04::RawWindowHandle::AndroidNdk(handle)
} else {
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
}
}
#[cfg(feature = "rwh_05")]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
use rwh_05::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
@@ -974,8 +966,29 @@ impl Window {
}
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
#[cfg(feature = "rwh_05")]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(native_window) = self.app.native_window().as_ref() {
let handle = rwh_06::AndroidNdkWindowHandle::new(native_window.ptr().cast());
Ok(rwh_06::RawWindowHandle::AndroidNdk(handle))
} else {
log::error!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
Err(rwh_06::HandleError::Unavailable)
}
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android(
rwh_06::AndroidDisplayHandle::new(),
))
}
pub fn config(&self) -> ConfigurationRef {
@@ -992,6 +1005,8 @@ impl Window {
None
}
pub fn set_content_protected(&self, _protected: bool) {}
pub fn has_focus(&self) -> bool {
*HAS_FOCUS.read().unwrap()
}

View File

@@ -16,19 +16,21 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use icrate::Foundation::{CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSProcessInfo};
use icrate::Foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel};
use once_cell::sync::Lazy;
use super::event_loop::{EventHandler, Never};
use super::uikit::UIView;
use super::view::WinitUIWindow;
use crate::{
dpi::LogicalSize,
dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::event_loop::{EventHandler, EventProxy, EventWrapper, Never},
window::WindowId as RootWindowId,
};
@@ -44,6 +46,19 @@ macro_rules! bug_assert {
};
}
#[derive(Debug)]
pub enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged(ScaleFactorChanged),
}
#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Id<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}
enum UserCallbackTransitionResult<'a> {
Success {
event_handler: Box<dyn EventHandler>,
@@ -57,7 +72,13 @@ enum UserCallbackTransitionResult<'a> {
impl Event<Never> {
fn is_redraw(&self) -> bool {
matches!(self, Event::RedrawRequested(_))
matches!(
self,
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
}
)
}
}
@@ -100,7 +121,7 @@ enum AppStateImpl {
Terminated,
}
struct AppState {
pub(crate) struct AppState {
// This should never be `None`, except for briefly during a state transition.
app_state: Option<AppStateImpl>,
control_flow: ControlFlow,
@@ -108,24 +129,18 @@ struct AppState {
}
impl AppState {
// requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> {
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
if cfg!(debug_assertions) {
assert_main_thread!(
"bug in winit: `AppState::get_mut()` can only be called on the main thread"
);
}
let mut guard = APP_STATE.borrow_mut();
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
#[cold]
unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(CFRunLoopGetMain());
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState {
app_state: Some(AppStateImpl::NotLaunched {
queued_windows: Vec::new(),
@@ -136,7 +151,7 @@ impl AppState {
waker,
});
}
init_guard(&mut guard)
init_guard(&mut guard);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
}
@@ -219,7 +234,7 @@ impl AppState {
};
self.set_state(AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Poll,
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
(windows, events)
@@ -275,7 +290,6 @@ impl AppState {
};
(waiting_event_handler, event)
}
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
@@ -428,12 +442,6 @@ impl AppState {
});
self.waker.start()
}
(_, ControlFlow::ExitWithCode(_)) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
self.control_flow = old
}
}
}
@@ -443,12 +451,18 @@ impl AppState {
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
}
}
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
self.control_flow = control_flow;
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow
}
}
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
let mut this = AppState::get_mut();
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
@@ -468,10 +482,8 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
window.makeKeyAndVisible();
}
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
let mut this = AppState::get_mut();
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_gpu_redraws,
@@ -500,24 +512,17 @@ pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
}
}
// requires main thread
pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut().will_launch_transition(queued_event_handler)
pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut(mtm).will_launch_transition(queued_event_handler)
}
// requires main thread
pub unsafe fn did_finish_launching() {
let mut this = AppState::get_mut();
pub fn did_finish_launching(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let windows = match this.state_mut() {
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
s => bug!("unexpected state {:?}", s),
};
// start waking up the event loop now!
bug_assert!(
this.control_flow == ControlFlow::Poll,
"unexpectedly not setup to `Poll` on launch!"
);
this.waker.start();
// have to drop RefMut because the window setup code below can trigger new events
@@ -535,7 +540,7 @@ pub unsafe fn did_finish_launching() {
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
let _: () = msg_send![&window, setScreen: ptr::null::<AnyObject>()];
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
window.setScreen(&screen);
let controller = window.rootViewController();
@@ -545,13 +550,13 @@ pub unsafe fn did_finish_launching() {
window.makeKeyAndVisible();
}
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)))
.chain(events);
handle_nonuser_events(events);
handle_nonuser_events(mtm, events);
// the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching
@@ -560,27 +565,27 @@ pub unsafe fn did_finish_launching() {
}
}
// requires main thread
// AppState::did_finish_launching handles the special transition `Init`
pub unsafe fn handle_wakeup_transition() {
let mut this = AppState::get_mut();
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let wakeup_event = match this.wakeup_transition() {
None => return,
Some(wakeup_event) => wakeup_event,
};
drop(this);
handle_nonuser_event(wakeup_event)
handle_nonuser_event(mtm, wakeup_event)
}
// requires main thread
pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) {
handle_nonuser_events(std::iter::once(event))
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
handle_nonuser_events(mtm, std::iter::once(event))
}
// requires main thread
pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
let mut this = AppState::get_mut();
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
mtm: MainThreadMarker,
events: I,
) {
let mut this = AppState::get_mut(mtm);
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
@@ -593,7 +598,6 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
processing_redraws,
} => (event_handler, active_control_flow, processing_redraws),
};
let mut control_flow = this.control_flow;
drop(this);
for wrapper in events {
@@ -607,16 +611,16 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
event_handler.handle_nonuser_event(event)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
}
}
loop {
let mut this = AppState::get_mut();
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
@@ -648,7 +652,6 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
active_control_flow,
}
});
this.control_flow = control_flow;
break;
}
drop(this);
@@ -664,20 +667,18 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
event_handler.handle_nonuser_event(event)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
}
}
}
}
// requires main thread
unsafe fn handle_user_events() {
let mut this = AppState::get_mut();
let mut control_flow = this.control_flow;
fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
@@ -694,10 +695,10 @@ unsafe fn handle_user_events() {
}
drop(this);
event_handler.handle_user_events(&mut control_flow);
event_handler.handle_user_events();
loop {
let mut this = AppState::get_mut();
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
@@ -718,28 +719,24 @@ unsafe fn handle_user_events() {
queued_gpu_redraws,
active_control_flow,
});
this.control_flow = control_flow;
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
EventWrapper::StaticEvent(event) => event_handler.handle_nonuser_event(event),
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
}
}
event_handler.handle_user_events(&mut control_flow);
event_handler.handle_user_events();
}
}
// requires main thread
pub unsafe fn handle_main_events_cleared() {
let mut this = AppState::get_mut();
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() {
return;
}
@@ -749,63 +746,44 @@ pub unsafe fn handle_main_events_cleared() {
};
drop(this);
handle_user_events();
handle_user_events(mtm);
let mut this = AppState::get_mut();
let mut this = AppState::get_mut(mtm);
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
.map(|window| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RedrawRequested,
})
})
.collect();
drop(this);
handle_nonuser_events(redraw_events);
handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait));
handle_nonuser_events(mtm, redraw_events);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
}
// requires main thread
pub unsafe fn handle_events_cleared() {
AppState::get_mut().events_cleared_transition();
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
// requires main thread
pub unsafe fn terminated() {
let mut this = AppState::get_mut();
pub fn terminated(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let mut event_handler = this.terminated_transition();
let mut control_flow = this.control_flow;
drop(this);
event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow)
event_handler.handle_nonuser_event(Event::LoopExiting)
}
fn handle_event_proxy(
event_handler: &mut Box<dyn EventHandler>,
control_flow: ControlFlow,
proxy: EventProxy,
) {
match proxy {
EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window,
),
}
}
fn handle_hidpi_proxy(
event_handler: &mut Box<dyn EventHandler>,
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window: Id<WinitUIWindow>,
) {
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor)));
fn handle_hidpi_proxy(event_handler: &mut Box<dyn EventHandler>, event: ScaleFactorChanged) {
let ScaleFactorChanged {
suggested_size,
scale_factor,
window,
} = event;
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
@@ -813,7 +791,7 @@ fn handle_hidpi_proxy(
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
event_handler.handle_nonuser_event(event, &mut control_flow);
event_handler.handle_nonuser_event(event);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);

View File

@@ -15,61 +15,81 @@ use core_foundation::runloop::{
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use icrate::Foundation::{MainThreadMarker, NSString};
use objc2::rc::Id;
use objc2::ClassType;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use crate::{
dpi::LogicalSize,
error::EventLoopError,
event::Event,
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
ControlFlow, DeviceEvents, EventLoopClosed,
EventLoopWindowTarget as RootEventLoopWindowTarget,
},
platform::ios::Idiom,
};
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle};
use super::{
app_state::AppState,
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
};
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitUIWindow>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
pub(super) mtm: MainThreadMarker,
p: PhantomData<T>,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(MainThreadMarker::new().unwrap())
monitor::uiscreens(self.mtm)
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
Some(MonitorHandle::new(UIScreen::main(self.mtm)))
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get_mut(self.mtm).set_control_flow(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
AppState::get_mut(self.mtm).control_flow()
}
pub(crate) fn exit(&self) {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
}
pub(crate) fn exiting(&self) -> bool {
false
}
}
pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootEventLoopWindowTarget<T>,
}
@@ -80,7 +100,8 @@ impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, EventLoopError> {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false;
unsafe {
@@ -92,16 +113,19 @@ impl<T: 'static> EventLoop<T> {
SINGLETON_INIT = true;
}
let (sender_to_clone, receiver) = mpsc::channel();
let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
Ok(EventLoop {
mtm,
sender,
receiver,
window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget {
receiver,
sender_to_clone,
mtm,
p: PhantomData,
},
_marker: PhantomData,
},
@@ -110,10 +134,10 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(self, event_handler: F) -> !
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
unsafe {
let application = UIApplication::shared(MainThreadMarker::new().unwrap());
let application = UIApplication::shared(self.mtm);
assert!(
application.is_none(),
"\
@@ -122,16 +146,17 @@ impl<T: 'static> EventLoop<T> {
);
let event_handler = std::mem::transmute::<
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow)>,
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>)>,
Box<EventHandlerCallback<T>>,
>(Box::new(event_handler));
let handler = EventLoopHandler {
f: event_handler,
receiver: self.receiver,
event_loop: self.window_target,
};
app_state::will_launch(Box::new(handler));
app_state::will_launch(self.mtm, Box::new(handler));
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
@@ -147,7 +172,7 @@ impl<T: 'static> EventLoop<T> {
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
EventLoopProxy::new(self.sender.clone())
}
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
@@ -158,9 +183,7 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
UIDevice::current(MainThreadMarker::new().unwrap())
.userInterfaceIdiom()
.into()
UIDevice::current(self.mtm).userInterfaceIdiom().into()
}
}
@@ -238,12 +261,11 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
_ => unreachable!(),
}
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
@@ -263,13 +285,12 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -279,13 +300,12 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -326,16 +346,16 @@ fn setup_control_flow_observers() {
#[derive(Debug)]
pub enum Never {}
type EventHandlerCallback<T> =
dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static;
type EventHandlerCallback<T> = dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>) + 'static;
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
fn handle_nonuser_event(&mut self, event: Event<Never>);
fn handle_user_events(&mut self);
}
struct EventLoopHandler<T: 'static> {
f: Box<EventHandlerCallback<T>>,
receiver: Receiver<T>,
event_loop: RootEventLoopWindowTarget<T>,
}
@@ -348,17 +368,13 @@ impl<T: 'static> Debug for EventLoopHandler<T> {
}
impl<T: 'static> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,
control_flow,
);
fn handle_nonuser_event(&mut self, event: Event<Never>) {
(self.f)(event.map_nonuser_event().unwrap(), &self.event_loop);
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
for event in self.event_loop.p.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
fn handle_user_events(&mut self) {
for event in self.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop);
}
}
}

View File

@@ -58,17 +58,6 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
if !::icrate::Foundation::is_main_thread() {
panic!($($t)*);
}
};
}
mod app_state;
mod event_loop;
mod ffi;

View File

@@ -2,11 +2,11 @@
use std::{
collections::{BTreeSet, VecDeque},
fmt,
ops::{Deref, DerefMut},
fmt, hash, ptr,
};
use icrate::Foundation::{MainThreadMarker, NSInteger};
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
use objc2::mutability::IsRetainable;
use objc2::rc::Id;
use super::uikit::{UIScreen, UIScreenMode};
@@ -16,32 +16,59 @@ use crate::{
platform_impl::platform::app_state,
};
// TODO(madsmtm): Remove or refactor this
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode>);
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self {
Self(
self.0
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
)
}
}
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)).hash(state);
}
}
impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm))
}
}
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: ScreenModeSendSync,
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
fn new(uiscreen: Id<UIScreen>, screen_mode: Id<UIScreenMode>) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
fn new(
uiscreen: Id<UIScreen>,
screen_mode: Id<UIScreenMode>,
mtm: MainThreadMarker,
) -> VideoMode {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate_millihertz,
screen_mode: ScreenModeSendSync(screen_mode),
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
monitor: MonitorHandle::new(uiscreen),
}
}
@@ -61,18 +88,40 @@ impl VideoMode {
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id<UIScreenMode> {
self.screen_mode.0.get(mtm)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner {
uiscreen: Id<UIScreen>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MonitorHandle {
inner: Inner,
ui_screen: MainThreadBound<Id<UIScreen>>,
}
impl Clone for MonitorHandle {
fn clone(&self) -> Self {
Self {
ui_screen: self
.ui_screen
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
}
}
}
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(self as *const Self).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other)
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
@@ -86,31 +135,6 @@ impl Ord for MonitorHandle {
}
}
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for MonitorHandle {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&mut self.inner
}
}
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Drop for MonitorHandle {
fn drop(&mut self) {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this using the proper fmt API
@@ -135,59 +159,80 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub(crate) fn new(uiscreen: Id<UIScreen>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
pub(crate) fn new(ui_screen: Id<UIScreen>) -> Self {
// Holding `Id<UIScreen>` implies we're on the main thread.
let mtm = MainThreadMarker::new().unwrap();
Self {
inner: Inner { uiscreen },
ui_screen: MainThreadBound::new(ui_screen, mtm),
}
}
}
impl Inner {
pub fn name(&self) -> Option<String> {
let main = UIScreen::main(MainThreadMarker::new().unwrap());
if self.uiscreen == main {
Some("Primary".to_string())
} else if self.uiscreen == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(MainThreadMarker::new().unwrap())
.iter()
.position(|rhs| rhs == &*self.uiscreen)
.map(|idx| idx.to_string())
}
self.ui_screen.get_on_main(|ui_screen, mtm| {
let main = UIScreen::main(mtm);
if *ui_screen == main {
Some("Primary".to_string())
} else if *ui_screen == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == &**ui_screen)
.map(|idx| idx.to_string())
}
})
}
pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self.uiscreen.nativeBounds();
let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self.uiscreen.nativeBounds();
let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
pub fn scale_factor(&self) -> f64 {
self.uiscreen.nativeScale() as f64
self.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(&self.uiscreen))
Some(
self.ui_screen
.get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
)
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = self
.uiscreen
.availableModes()
.into_iter()
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
})
.collect();
self.ui_screen.get_on_main(|ui_screen, mtm| {
// Use Ord impl of RootVideoMode
modes.into_iter().map(|mode| mode.video_mode)
let modes: BTreeSet<_> = ui_screen
.availableModes()
.into_iter()
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(ui_screen.clone(), mode, mtm),
})
.collect();
modes.into_iter().map(|mode| mode.video_mode)
})
}
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id<UIScreen> {
self.ui_screen.get(mtm)
}
pub fn preferred_video_mode(&self) -> VideoMode {
self.ui_screen.get_on_main(|ui_screen, mtm| {
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
})
}
}
@@ -215,20 +260,6 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
refresh_rate_millihertz as u32 * 1000
}
// MonitorHandleExtIOS
impl Inner {
pub(crate) fn ui_screen(&self) -> &Id<UIScreen> {
&self.uiscreen
}
pub fn preferred_video_mode(&self) -> VideoMode {
VideoMode::new(
self.uiscreen.clone(),
self.uiscreen.preferredMode().unwrap(),
)
}
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()

View File

@@ -1,4 +1,3 @@
#![deny(unsafe_op_in_unsafe_fn)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]

View File

@@ -8,6 +8,7 @@ use objc2::rc::Id;
use objc2::runtime::AnyClass;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
use super::app_state::{self, EventWrapper};
use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController,
@@ -19,8 +20,6 @@ use crate::{
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations,
platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
ffi::{UIRectEdge, UIUserInterfaceIdiom},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen,
@@ -41,17 +40,21 @@ declare_class!(
unsafe impl WinitView {
#[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) {
let mtm = MainThreadMarker::new().unwrap();
let window = self.window().unwrap();
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.id()),
)));
}
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RedrawRequested,
}),
);
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
#[method(layoutSubviews)]
fn layout_subviews(&self) {
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
let window = self.window().unwrap();
@@ -74,16 +77,18 @@ declare_class!(
self.setFrame(window_bounds);
}
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(size),
}));
}
}),
);
}
#[method(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let mtm = MainThreadMarker::new().unwrap();
let _: () =
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
@@ -111,25 +116,26 @@ declare_class!(
let screen_space = screen.coordinateSpace();
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
#[method(touchesBegan:withEvent:)]
@@ -254,9 +260,8 @@ impl WinitView {
}),
}));
}
unsafe {
app_state::handle_nonuser_events(touch_events);
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, touch_events);
}
}
@@ -430,23 +435,27 @@ declare_class!(
unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)]
fn become_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(true),
}));
}
}),
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
#[method(resignKeyWindow)]
fn resign_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(false),
}));
}
}),
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
}
@@ -454,7 +463,7 @@ declare_class!(
impl WinitUIWindow {
pub(crate) fn new(
_mtm: MainThreadMarker,
mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
@@ -467,12 +476,12 @@ impl WinitUIWindow {
match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
let screen = monitor.ui_screen();
screen.setCurrentMode(Some(&video_mode.screen_mode.0));
let screen = monitor.ui_screen(mtm);
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
this.setScreen(screen);
}
Some(Fullscreen::Borderless(Some(ref monitor))) => {
let screen = monitor.ui_screen();
let screen = monitor.ui_screen(mtm);
this.setScreen(screen);
}
_ => (),
@@ -499,27 +508,31 @@ declare_class!(
unsafe impl WinitApplicationDelegate {
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
unsafe {
app_state::did_finish_launching();
}
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
true
}
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
}
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
}
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, _application: &UIApplication) {}
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, _application: &UIApplication) {}
fn did_enter_background(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
@@ -538,10 +551,37 @@ declare_class!(
}));
}
}
unsafe {
app_state::handle_nonuser_events(events);
app_state::terminated();
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
app_state::terminated(mtm);
}
#[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))
}
}
);
impl WinitApplicationDelegate {
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
let mut events = Vec::new();
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
}
}

View File

@@ -6,8 +6,8 @@ use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, Main
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{class, msg_send};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
@@ -17,9 +17,7 @@ use crate::{
icon::Icon,
platform::ios::{ScreenEdge, ValidOrientations},
platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -43,6 +41,10 @@ impl Inner {
debug!("`Window::set_transparent` is ignored on iOS")
}
pub fn set_blur(&self, _blur: bool) {
debug!("`Window::set_blur` is ignored on iOS")
}
pub fn set_visible(&self, visible: bool) {
self.window.setHidden(!visible)
}
@@ -53,20 +55,19 @@ impl Inner {
}
pub fn request_redraw(&self) {
unsafe {
if self.gl_or_metal_backed {
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided.
//
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window.clone());
} else {
self.view.setNeedsDisplay();
}
if self.gl_or_metal_backed {
let mtm = MainThreadMarker::new().unwrap();
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided.
//
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(mtm, self.window.clone());
} else {
self.view.setNeedsDisplay();
}
}
@@ -196,6 +197,9 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -219,14 +223,17 @@ impl Inner {
}
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let mtm = MainThreadMarker::new().unwrap();
let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen();
uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
let uiscreen = video_mode.monitor.ui_screen(mtm);
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
uiscreen.clone()
}
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
Some(Fullscreen::Borderless(None)) => {
self.current_monitor_inner().ui_screen(mtm).clone()
}
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
@@ -249,8 +256,9 @@ impl Inner {
}
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
let mtm = MainThreadMarker::new().unwrap();
let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen();
let uiscreen = monitor.ui_screen(mtm);
let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds();
@@ -323,16 +331,47 @@ impl Inner {
self.window.id()
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = UiKitWindowHandle::empty();
#[cfg(feature = "rwh_04")]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::UiKitHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
RawWindowHandle::UiKit(window_handle)
rwh_04::RawWindowHandle::UiKit(window_handle)
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
#[cfg(feature = "rwh_05")]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::UiKitWindowHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
rwh_05::RawWindowHandle::UiKit(window_handle)
}
#[cfg(feature = "rwh_05")]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let mut window_handle = rwh_06::UiKitWindowHandle::new({
let ui_view = Id::as_ptr(&self.view) as _;
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
});
window_handle.ui_view_controller =
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
Ok(rwh_06::RawWindowHandle::UiKit(window_handle))
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
pub fn theme(&self) -> Option<Theme> {
@@ -340,6 +379,8 @@ impl Inner {
None
}
pub fn set_content_protected(&self, _protected: bool) {}
pub fn has_focus(&self) -> bool {
self.window.isKeyWindow()
}
@@ -365,11 +406,11 @@ pub struct Window {
impl Window {
pub(crate) fn new<T>(
_event_loop: &EventLoopWindowTarget<T>,
event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
let mtm = MainThreadMarker::new().unwrap();
let mtm = event_loop.mtm;
if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
@@ -383,8 +424,8 @@ impl Window {
let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(),
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(None)) | None => &main_screen,
};
@@ -424,7 +465,7 @@ impl Window {
&view_controller,
);
unsafe { app_state::set_key_window(&window) };
app_state::set_key_window(mtm, &window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
@@ -436,25 +477,26 @@ impl Window {
let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window: window.clone(),
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
let inner = Inner {

View File

@@ -13,7 +13,7 @@ use xkbcommon_dl::{
XkbCommonCompose,
};
#[cfg(feature = "wayland")]
use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd};
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
#[cfg(feature = "x11")]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
@@ -250,37 +250,43 @@ impl KbdState {
.unwrap_or_else(|| "C".into());
let locale = CString::new(locale.into_vec()).unwrap();
let compose_table = (XKBCH.xkb_compose_table_new_from_locale)(
self.xkb_context,
locale.as_ptr(),
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
);
let compose_table = unsafe {
(XKBCH.xkb_compose_table_new_from_locale)(
self.xkb_context,
locale.as_ptr(),
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
)
};
if compose_table.is_null() {
// init of compose table failed, continue without compose
return;
}
let compose_state = (XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
);
let compose_state = unsafe {
(XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
)
};
if compose_state.is_null() {
// init of compose state failed, continue without compose
(XKBCH.xkb_compose_table_unref)(compose_table);
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
return;
}
let compose_state_2 = (XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
);
let compose_state_2 = unsafe {
(XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
)
};
if compose_state_2.is_null() {
// init of compose state failed, continue without compose
(XKBCH.xkb_compose_table_unref)(compose_table);
(XKBCH.xkb_compose_state_unref)(compose_state);
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
unsafe { (XKBCH.xkb_compose_state_unref)(compose_state) };
return;
}
@@ -296,62 +302,71 @@ impl KbdState {
}
unsafe fn de_init(&mut self) {
(XKBH.xkb_state_unref)(self.xkb_state);
unsafe { (XKBH.xkb_state_unref)(self.xkb_state) };
self.xkb_state = ptr::null_mut();
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
unsafe { (XKBH.xkb_keymap_unref)(self.xkb_keymap) };
self.xkb_keymap = ptr::null_mut();
}
#[cfg(feature = "x11")]
pub unsafe fn init_with_x11_keymap(&mut self) {
if !self.xkb_keymap.is_null() {
self.de_init();
unsafe { self.de_init() };
}
// TODO: Support keyboards other than the "virtual core keyboard device".
self.core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection);
let keymap = (XKBXH.xkb_x11_keymap_new_from_device)(
self.xkb_context,
self.xcb_connection,
self.core_keyboard_id,
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
self.core_keyboard_id =
unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection) };
let keymap = unsafe {
(XKBXH.xkb_x11_keymap_new_from_device)(
self.xkb_context,
self.xcb_connection,
self.core_keyboard_id,
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
)
};
if keymap.is_null() {
panic!("Failed to get keymap from X11 server.");
}
let state = (XKBXH.xkb_x11_state_new_from_device)(
keymap,
self.xcb_connection,
self.core_keyboard_id,
);
self.post_init(state, keymap);
let state = unsafe {
(XKBXH.xkb_x11_state_new_from_device)(
keymap,
self.xcb_connection,
self.core_keyboard_id,
)
};
unsafe { self.post_init(state, keymap) };
}
#[cfg(feature = "wayland")]
pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) {
if !self.xkb_keymap.is_null() {
self.de_init();
unsafe { self.de_init() };
}
let map = MmapOptions::new()
.len(size)
.map_copy_read_only(&fd)
.unwrap();
let map = unsafe {
MmapOptions::new()
.len(size)
.map_copy_read_only(&fd)
.unwrap()
};
let keymap = (XKBH.xkb_keymap_new_from_string)(
self.xkb_context,
map.as_ptr() as *const _,
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
let keymap = unsafe {
(XKBH.xkb_keymap_new_from_string)(
self.xkb_context,
map.as_ptr() as *const _,
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
)
};
if keymap.is_null() {
panic!("Received invalid keymap from compositor.");
}
let state = (XKBH.xkb_state_new)(keymap);
self.post_init(state, keymap);
let state = unsafe { (XKBH.xkb_state_new)(keymap) };
unsafe { self.post_init(state, keymap) };
}
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {

View File

@@ -11,7 +11,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
#[cfg(x11_platform)]
use once_cell::sync::Lazy;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use smol_str::SmolStr;
#[cfg(x11_platform)]
@@ -19,7 +18,7 @@ use crate::platform::x11::XlibErrorHook;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, KeyEvent},
event::KeyEvent,
event_loop::{
AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
EventLoopWindowTarget as RootELW,
@@ -178,9 +177,9 @@ pub enum DeviceId {
impl DeviceId {
pub const unsafe fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(wayland::DeviceId::dummy());
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(x11::DeviceId::dummy());
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
}
}
@@ -283,7 +282,7 @@ impl VideoMode {
#[inline]
pub fn monitor(&self) -> MonitorHandle {
x11_or_wayland!(match self; VideoMode(m) => m.monitor())
x11_or_wayland!(match self; VideoMode(m) => m.monitor(); as MonitorHandle)
}
}
@@ -316,12 +315,7 @@ impl Window {
#[inline]
pub fn id(&self) -> WindowId {
match self {
#[cfg(wayland_platform)]
Self::Wayland(window) => window.id(),
#[cfg(x11_platform)]
Self::X(window) => window.id(),
}
x11_or_wayland!(match self; Window(w) => w.id())
}
#[inline]
@@ -334,6 +328,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
}
#[inline]
pub fn set_blur(&self, blur: bool) {
x11_or_wayland!(match self; Window(w) => w.set_blur(blur));
}
#[inline]
pub fn set_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
@@ -444,6 +443,11 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
}
#[inline]
pub fn show_window_menu(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.show_window_menu(position))
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
@@ -500,23 +504,13 @@ impl Window {
}
#[inline]
pub fn set_window_level(&self, _level: WindowLevel) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_level(_level),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
pub fn set_window_level(&self, level: WindowLevel) {
x11_or_wayland!(match self; Window(w) => w.set_window_level(level))
}
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner)))
}
#[inline]
@@ -541,12 +535,7 @@ impl Window {
#[inline]
pub fn focus_window(&self) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.focus_window(),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
x11_or_wayland!(match self; Window(w) => w.focus_window())
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
@@ -564,18 +553,7 @@ impl Window {
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(current_monitor)
}
#[cfg(wayland_platform)]
Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(current_monitor)
}
}
Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle))
}
#[inline]
@@ -598,25 +576,39 @@ impl Window {
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
let primary_monitor = MonitorHandle::X(window.primary_monitor());
Some(primary_monitor)
}
#[cfg(wayland_platform)]
Window::Wayland(ref window) => window.primary_monitor(),
}
Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle))
}
#[cfg(feature = "rwh_04")]
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle())
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_04())
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_05())
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06())
}
#[inline]
@@ -629,6 +621,10 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.theme())
}
pub fn set_content_protected(&self, protected: bool) {
x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected))
}
#[inline]
pub fn has_focus(&self) -> bool {
x11_or_wayland!(match self; Window(window) => window.has_focus())
@@ -684,26 +680,31 @@ unsafe extern "C" fn x_error_callback(
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
);
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
unsafe {
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
)
};
let description =
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
let error = XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
let error = unsafe {
XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
}
};
// Don't log error.
@@ -751,21 +752,34 @@ impl<T: 'static> EventLoop<T> {
}
// NOTE: Wayland first because of X11 could be present under wayland as well.
#[cfg(wayland_platform)]
if attributes.forced_backend == Some(Backend::Wayland)
|| env::var("WAYLAND_DISPLAY").is_ok()
{
return EventLoop::new_wayland_any_thread().map_err(Into::into);
}
let backend = match (
attributes.forced_backend,
env::var("WAYLAND_DISPLAY").is_ok(),
env::var("DISPLAY").is_ok(),
) {
// User is forcing a backend.
(Some(backend), _, _) => backend,
// Wayland is present.
#[cfg(wayland_platform)]
(None, true, _) => Backend::Wayland,
// X11 is present.
#[cfg(x11_platform)]
(None, _, true) => Backend::X,
// No backend is present.
_ => {
return Err(EventLoopError::Os(os_error!(OsError::Misc(
"neither WAYLAND_DISPLAY nor DISPLAY is set."
))));
}
};
#[cfg(x11_platform)]
if attributes.forced_backend == Some(Backend::X) || env::var("DISPLAY").is_ok() {
return Ok(EventLoop::new_x11_any_thread().unwrap());
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
#[cfg(x11_platform)]
Backend::X => Ok(EventLoop::new_x11_any_thread().unwrap()),
}
Err(EventLoopError::Os(os_error!(OsError::Misc(
"neither WAYLAND_DISPLAY nor DISPLAY is set."
))))
}
#[cfg(wayland_platform)]
@@ -789,21 +803,21 @@ impl<T: 'static> EventLoop<T> {
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>),
{
self.run_ondemand(callback)
self.run_on_demand(callback)
}
pub fn run_ondemand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_ondemand(callback))
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
@@ -843,61 +857,63 @@ impl<T> EventLoopWindowTarget<T> {
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref evlp) => evlp
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp
.x_connection()
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
}
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
match *self {
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => {
let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
Some(primary_monitor)
evlp.available_monitors().map(MonitorHandle::X).collect()
}
}
}
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
match *self {
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(_) => (),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp.set_listen_device_events(_allowed),
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(
x11_or_wayland!(match self; EventLoopWindowTarget(evlp) => evlp.primary_monitor()?; as MonitorHandle),
)
}
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle())
#[inline]
pub fn listen_device_events(&self, allowed: DeviceEvents) {
x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed))
}
}
fn sticky_exit_callback<T, F>(
evt: Event<T>,
target: &RootELW<T>,
control_flow: &mut ControlFlow,
callback: &mut F,
) where
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
if let ControlFlow::ExitWithCode(code) = *control_flow {
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
} else {
callback(evt, target, control_flow)
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_06())
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_control_flow(control_flow))
}
pub(crate) fn control_flow(&self) -> ControlFlow {
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
}
pub(crate) fn exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.exit())
}
pub(crate) fn exiting(&self) -> bool {
x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
}
fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
}
fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
}
}

View File

@@ -1,6 +1,6 @@
//! The event-loop routines.
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
@@ -9,20 +9,20 @@ use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
use sctk::reexports::calloop;
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
use sctk::reexports::client::{Connection, QueueHandle};
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::event_loop::{
ControlFlow, DeviceEvents, EventLoopWindowTarget as RootEventLoopWindowTarget,
};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError};
mod proxy;
@@ -39,12 +39,9 @@ type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>,
/// The Wayland event loop.
pub struct EventLoop<T: 'static> {
/// Has `run` or `run_ondemand` been called or a call to `pump_events` that starts the loop
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
loop_running: bool,
/// The application's latest control_flow state
control_flow: ControlFlow,
buffer_sink: EventSink,
compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>,
@@ -104,7 +101,7 @@ impl<T: 'static> EventLoop<T> {
)?;
// Register Wayland source.
let wayland_source = map_err!(WaylandSource::new(event_queue), WaylandError::Wire)?;
let wayland_source = WaylandSource::new(connection.clone(), event_queue);
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| {
let result = queue.dispatch_pending(winit_state);
@@ -166,13 +163,14 @@ impl<T: 'static> EventLoop<T> {
wayland_dispatcher: wayland_dispatcher.clone(),
event_loop_awakener,
queue_handle,
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
state: RefCell::new(winit_state),
_marker: PhantomData,
};
let event_loop = Self {
loop_running: false,
control_flow: ControlFlow::default(),
compositor_updates: Vec::new(),
buffer_sink: EventSink::default(),
window_ids: Vec::new(),
@@ -190,9 +188,9 @@ impl<T: 'static> EventLoop<T> {
Ok(event_loop)
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
@@ -213,7 +211,7 @@ impl<T: 'static> EventLoop<T> {
};
// Applications aren't allowed to carry windows between separate
// `run_ondemand` calls but if they have only just dropped their
// `run_on_demand` calls but if they have only just dropped their
// windows we need to make sure those last requests are sent to the
// compositor.
let _ = self.roundtrip().map_err(EventLoopError::Os);
@@ -223,35 +221,24 @@ impl<T: 'static> EventLoop<T> {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
if !self.loop_running {
self.loop_running = true;
// Reset the internal state for the loop as we start running to
// ensure consistent behaviour in case the loop runs and exits more
// than once.
self.control_flow = ControlFlow::Poll;
// Run the initial loop iteration.
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
}
if let ControlFlow::ExitWithCode(code) = self.control_flow {
if let Some(code) = self.exit_code() {
self.loop_running = false;
let mut dummy = self.control_flow;
sticky_exit_callback(
Event::LoopExiting,
self.window_target(),
&mut dummy,
&mut callback,
);
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
@@ -261,62 +248,18 @@ impl<T: 'static> EventLoop<T> {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
let cause = loop {
let start = Instant::now();
// TODO(rib): remove this workaround and instead make sure that the calloop
// WaylandSource correctly implements the cooperative prepare_read protocol
// that support multithreaded wayland clients that may all read from the
// same socket.
//
// During the run of the user callback, some other code monitoring and reading the
// Wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the Wayland socket, to avoid delaying the
// dispatch of these events until we're woken up again.
let instant_wakeup = {
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let queue = wayland_source.queue();
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
}
#[cfg(x11_platform)]
_ => unreachable!(),
};
match queue.dispatch_pending(state) {
Ok(dispatched) => {
state.dispatched_events |= !state.events_sink.is_empty()
|| !state.window_compositor_updates.is_empty();
dispatched > 0
}
Err(error) => {
error!("Error dispatching wayland queue: {}", error);
self.control_flow = ControlFlow::ExitWithCode(1);
return;
}
}
};
timeout = if instant_wakeup {
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow {
timeout = {
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))
}
// This function shouldn't have to handle any requests to exit
// the application (there should be no need to poll for events
// if the application has requested to exit) so we consider
// it a bug in the backend if we ever see `ExitWithCode` here.
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
min_timeout(control_flow_timeout, timeout)
};
@@ -324,7 +267,13 @@ impl<T: 'static> EventLoop<T> {
// NOTE Ideally we should flush as the last thing we do before polling
// to wait for events, and this should be done by the calloop
// WaylandSource but we currently need to flush writes manually.
let _ = self.connection.flush();
//
// Checking for flush error is essential to perform an exit with error, since
// once we have a protocol error, we could get stuck retrying...
if self.connection.flush().is_err() {
self.set_exit_code(1);
return;
}
if let Err(error) = self.loop_dispatch(timeout) {
// NOTE We exit on errors from dispatches, since if we've got protocol error
@@ -334,13 +283,13 @@ impl<T: 'static> EventLoop<T> {
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = error.raw_os_error().unwrap_or(1);
self.control_flow = ControlFlow::ExitWithCode(exit_code);
self.set_exit_code(exit_code);
return;
}
// NB: `StartCause::Init` is handled as a special case and doesn't need
// to be considered here
let cause = match self.control_flow {
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
@@ -359,11 +308,6 @@ impl<T: 'static> EventLoop<T> {
}
}
}
// This function shouldn't have to handle any requests to exit
// the application (there should be no need to poll for events
// if the application has requested to exit) so we consider
// it a bug in the backend if we ever see `ExitWithCode` here.
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
// Reduce spurious wake-ups.
@@ -378,14 +322,12 @@ impl<T: 'static> EventLoop<T> {
self.single_iteration(&mut callback, cause);
}
fn single_iteration<F>(&mut self, mut callback: &mut F, cause: StartCause)
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
// NOTE currently just indented to simplify the diff
let mut control_flow = self.control_flow;
// We retain these grow-only scratch buffers as part of the EventLoop
// for the sake of avoiding lots of reallocs. We take them here to avoid
// trying to mutably borrow `self` more than once and we swap them back
@@ -394,33 +336,18 @@ impl<T: 'static> EventLoop<T> {
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
sticky_exit_callback(
Event::NewEvents(cause),
&self.window_target,
&mut control_flow,
callback,
);
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 {
sticky_exit_callback(
Event::Resumed,
&self.window_target,
&mut control_flow,
callback,
);
callback(Event::Resumed, &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(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
&self.window_target,
&mut control_flow,
&mut callback,
);
callback(Event::UserEvent(user_event), &self.window_target);
}
// Drain the pending compositor updates.
@@ -443,7 +370,7 @@ impl<T: 'static> EventLoop<T> {
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
sticky_exit_callback(
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
@@ -454,8 +381,6 @@ impl<T: 'static> EventLoop<T> {
},
},
&self.window_target,
&mut control_flow,
&mut callback,
);
let physical_size = *new_inner_size.lock().unwrap();
@@ -497,26 +422,22 @@ impl<T: 'static> EventLoop<T> {
physical_size
});
sticky_exit_callback(
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
if compositor_update.close_window {
sticky_exit_callback(
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
@@ -527,7 +448,7 @@ impl<T: 'static> EventLoop<T> {
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
callback(event, &self.window_target);
}
// Handle non-synthetic events.
@@ -536,7 +457,7 @@ impl<T: 'static> EventLoop<T> {
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
callback(event, &self.window_target);
}
// Collect the window ids
@@ -579,11 +500,12 @@ impl<T: 'static> EventLoop<T> {
});
if request_redraw {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(window_id)),
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::RedrawRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
@@ -594,14 +516,8 @@ impl<T: 'static> EventLoop<T> {
});
// This is always the last event we dispatch before poll again
sticky_exit_callback(
Event::AboutToWait,
&self.window_target,
&mut control_flow,
&mut callback,
);
callback(Event::AboutToWait, &self.window_target);
self.control_flow = control_flow;
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
std::mem::swap(&mut self.window_ids, &mut window_ids);
@@ -655,6 +571,22 @@ impl<T: 'static> EventLoop<T> {
))))
})
}
fn control_flow(&self) -> ControlFlow {
self.window_target.p.control_flow()
}
fn exiting(&self) -> bool {
self.window_target.p.exiting()
}
fn set_exit_code(&self, code: i32) {
self.window_target.p.set_exit_code(code)
}
fn exit_code(&self) -> Option<i32> {
self.window_target.p.exit_code()
}
}
pub struct EventLoopWindowTarget<T> {
@@ -664,6 +596,12 @@ pub struct EventLoopWindowTarget<T> {
/// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>,
/// The application's latest control_flow state
pub(crate) control_flow: Cell<ControlFlow>,
/// The application's exit state.
pub(crate) exit: Cell<Option<i32>>,
// TODO remove that RefCell once we can pass `&mut` in `Window::new`.
/// Winit state.
pub state: RefCell<WinitState>,
@@ -678,10 +616,31 @@ pub struct EventLoopWindowTarget<T> {
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
use sctk::reexports::client::Proxy;
let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
rwh_05::RawDisplayHandle::Wayland(display_handle)
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use sctk::reexports::client::Proxy;
Ok(rwh_06::WaylandDisplayHandle::new({
let ptr = self.connection.display().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null")
})
.into())
}
}

View File

@@ -3,29 +3,51 @@ use sctk::reexports::client::Proxy;
use sctk::output::OutputData;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl::platform::{
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
};
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::event_loop::ControlFlow;
use crate::platform_impl::platform::VideoMode as PlatformVideoMode;
use super::event_loop::EventLoopWindowTarget;
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.state
.borrow()
.output_state
.outputs()
.map(MonitorHandle::new)
.collect()
}
#[inline]
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
// There's no primary monitor on Wayland.
None
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
}
#[derive(Clone, Debug)]
@@ -70,7 +92,18 @@ impl MonitorHandle {
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| info.location).into()
output_data.with_output_info(|info| {
info.logical_position.map_or_else(
|| {
LogicalPosition::<i32>::from(info.location)
.to_physical(info.scale_factor as f64)
},
|logical_position| {
LogicalPosition::<i32>::from(logical_position)
.to_physical(info.scale_factor as f64)
},
)
})
}
#[inline]
@@ -157,7 +190,7 @@ impl VideoMode {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> PlatformMonitorHandle {
PlatformMonitorHandle::Wayland(self.monitor.clone())
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}

View File

@@ -67,7 +67,11 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
};
// Drop the repeat, if there were any.
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
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);
}
// The keyboard focus is considered as general focus.
state
@@ -89,7 +93,11 @@ 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.
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
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);
}
// NOTE: The check whether the window exists is essential as we might get a
// nil surface, regardless of what protocol says.
@@ -114,9 +122,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
}
WlKeyboardEvent::Key {
key,
state: key_state,
state: WEnum::Value(WlKeyState::Pressed),
..
} if key_state == WEnum::Value(WlKeyState::Pressed) => {
} => {
let key = key + 8;
key_input(
@@ -184,9 +192,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
}
WlKeyboardEvent::Key {
key,
state: key_state,
state: WEnum::Value(WlKeyState::Released),
..
} if key_state == WEnum::Value(WlKeyState::Released) => {
} => {
let key = key + 8;
key_input(
@@ -204,6 +212,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
&& Some(key) == keyboard_state.current_repeat
{
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
}
}
}
WlKeyboardEvent::Modifiers {

View File

@@ -2,7 +2,7 @@
use std::sync::Arc;
use fnv::FnvHashMap;
use ahash::AHashMap;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
@@ -29,7 +29,7 @@ use keyboard::{KeyboardData, KeyboardState};
use text_input::TextInputData;
use touch::TouchPoint;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct WinitSeatState {
/// The pointer bound on the seat.
pointer: Option<Arc<ThemedPointer<WinitPointerData>>>,
@@ -38,7 +38,7 @@ pub struct WinitSeatState {
touch: Option<WlTouch>,
/// The mapping from touched points to the surfaces they're present.
touch_map: FnvHashMap<i32, TouchPoint>,
touch_map: AHashMap<i32, TouchPoint>,
/// The text input bound on the seat.
text_input: Option<Arc<ZwpTextInputV3>>,
@@ -58,16 +58,7 @@ pub struct WinitSeatState {
impl WinitSeatState {
pub fn new() -> Self {
Self {
pointer: None,
touch: None,
relative_pointer: None,
text_input: None,
touch_map: Default::default(),
keyboard_state: None,
modifiers: ModifiersState::empty(),
modifiers_pending: false,
}
Default::default()
}
}
@@ -97,12 +88,14 @@ impl SeatHandler for WinitState {
SeatCapability::Pointer if seat_state.pointer.is_none() => {
let surface = self.compositor_state.create_surface(queue_handle);
let surface_id = surface.id();
let pointer_data = WinitPointerData::new(seat.clone(), surface);
let pointer_data = WinitPointerData::new(seat.clone());
let themed_pointer = self
.seat_state
.get_pointer_with_theme_and_data(
queue_handle,
&seat,
self.shm.wl_shm(),
surface,
ThemeSpec::System,
pointer_data,
)
@@ -167,7 +160,7 @@ impl SeatHandler for WinitState {
let pointer_data = pointer.pointer().winit_data();
// Remove the cursor from the mapping.
let surface_id = pointer_data.cursor_surface().id();
let surface_id = pointer.surface().id();
let _ = self.pointer_surfaces.remove(&surface_id);
// Remove the inner locks/confines before dropping the pointer.

View File

@@ -2,6 +2,7 @@
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
@@ -10,15 +11,17 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch};
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1;
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::compositor::SurfaceData;
use sctk::globals::GlobalData;
use sctk::seat::pointer::{PointerData, PointerDataExt};
use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler};
use sctk::seat::SeatState;
use sctk::shell::xdg::frame::FrameClick;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
@@ -67,35 +70,31 @@ impl PointerHandler for WinitState {
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. }
if parent_surface != surface =>
{
if let Some(icon) =
window.frame_point_moved(seat, surface, event.position.0, event.position.1)
{
if let Some(icon) = window.frame_point_moved(
seat,
surface,
Duration::ZERO,
event.position.0,
event.position.1,
) {
if let Some(pointer) = seat_state.pointer.as_ref() {
let surface = pointer
.pointer()
.data::<WinitPointerData>()
.unwrap()
.cursor_surface();
let scale_factor =
surface.data::<SurfaceData>().unwrap().scale_factor();
let _ = pointer.set_cursor(
connection,
icon,
self.shm.wl_shm(),
surface,
scale_factor,
);
let _ = pointer.set_cursor(connection, icon);
}
}
}
PointerEventKind::Leave { .. } if parent_surface != surface => {
window.frame_point_left();
}
ref kind @ PointerEventKind::Press { button, serial, .. }
| ref kind @ PointerEventKind::Release { button, serial, .. }
if parent_surface != surface =>
{
ref kind @ PointerEventKind::Press {
button,
serial,
time,
}
| ref kind @ PointerEventKind::Release {
button,
serial,
time,
} if parent_surface != surface => {
let click = match wayland_button_to_winit(button) {
MouseButton::Left => FrameClick::Normal,
MouseButton::Right => FrameClick::Alternate,
@@ -109,6 +108,7 @@ impl PointerHandler for WinitState {
pressed,
seat,
serial,
Duration::from_millis(time as u64),
window_id,
&mut self.window_compositor_updates,
);
@@ -238,9 +238,6 @@ impl PointerHandler for WinitState {
#[derive(Debug)]
pub struct WinitPointerData {
/// The surface associated with this pointer, which is used for icons.
cursor_surface: WlSurface,
/// The inner winit data associated with the pointer.
inner: Mutex<WinitPointerDataInner>,
@@ -249,9 +246,8 @@ pub struct WinitPointerData {
}
impl WinitPointerData {
pub fn new(seat: WlSeat, surface: WlSurface) -> Self {
pub fn new(seat: WlSeat) -> Self {
Self {
cursor_surface: surface,
inner: Mutex::new(WinitPointerDataInner::default()),
sctk_data: PointerData::new(seat),
}
@@ -313,11 +309,6 @@ impl WinitPointerData {
self.sctk_data.seat()
}
/// The WlSurface used to set cursor theme.
pub fn cursor_surface(&self) -> &WlSurface {
&self.cursor_surface
}
/// Active window.
pub fn focused_window(&self) -> Option<WindowId> {
self.inner.lock().unwrap().surface
@@ -325,7 +316,7 @@ impl WinitPointerData {
/// Last button serial.
pub fn latest_button_serial(&self) -> u32 {
self.inner.lock().unwrap().latest_button_serial
self.sctk_data.latest_button_serial().unwrap_or_default()
}
/// Last enter serial.
@@ -341,12 +332,6 @@ impl WinitPointerData {
}
}
impl Drop for WinitPointerData {
fn drop(&mut self) {
self.cursor_surface.destroy();
}
}
impl PointerDataExt for WinitPointerData {
fn pointer_data(&self) -> &PointerData {
&self.sctk_data
@@ -486,7 +471,35 @@ impl Dispatch<ZwpConfinedPointerV1, GlobalData, WinitState> for PointerConstrain
}
}
impl Dispatch<WpCursorShapeDeviceV1, GlobalData, WinitState> for SeatState {
fn event(
_: &mut WinitState,
_: &WpCursorShapeDeviceV1,
_: <WpCursorShapeDeviceV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
unreachable!("wp_cursor_shape_manager has no events")
}
}
impl Dispatch<WpCursorShapeManagerV1, GlobalData, WinitState> for SeatState {
fn event(
_: &mut WinitState,
_: &WpCursorShapeManagerV1,
_: <WpCursorShapeManagerV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
unreachable!("wp_cursor_device_manager has no events")
}
}
delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState);
delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState);
delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState);
delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState);

View File

@@ -121,7 +121,7 @@ impl TouchHandler for WinitState {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Cancelled,
phase: TouchPhase::Moved,
location: touch_point.location.to_physical(scale_factor),
force: None,
id: id as u64,

View File

@@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use fnv::FnvHashMap;
use ahash::AHashMap;
use sctk::reexports::calloop::LoopHandle;
use sctk::reexports::client::backend::ObjectId;
@@ -31,6 +31,7 @@ use super::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState,
};
use super::types::kwin_blur::KWinBlurManager;
use super::types::wp_fractional_scaling::FractionalScalingManager;
use super::types::wp_viewporter::ViewporterState;
use super::types::xdg_activation::XdgActivationState;
@@ -61,10 +62,10 @@ pub struct WinitState {
pub xdg_shell: XdgShell,
/// The currently present windows.
pub windows: RefCell<FnvHashMap<WindowId, Arc<Mutex<WindowState>>>>,
pub windows: RefCell<AHashMap<WindowId, Arc<Mutex<WindowState>>>>,
/// The requests from the `Window` to EventLoop, such as close operations and redraw requests.
pub window_requests: RefCell<FnvHashMap<WindowId, Arc<WindowRequests>>>,
pub window_requests: RefCell<AHashMap<WindowId, Arc<WindowRequests>>>,
/// The events that were generated directly from the window.
pub window_events_sink: Arc<Mutex<EventSink>>,
@@ -73,10 +74,10 @@ pub struct WinitState {
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
/// Currently handled seats.
pub seats: FnvHashMap<ObjectId, WinitSeatState>,
pub seats: AHashMap<ObjectId, WinitSeatState>,
/// Currently present cursor surfaces.
pub pointer_surfaces: FnvHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
pub pointer_surfaces: AHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
/// The state of the text input on the client.
pub text_input_state: Option<TextInputState>,
@@ -103,6 +104,9 @@ pub struct WinitState {
/// Fractional scaling manager.
pub fractional_scaling_manager: Option<FractionalScalingManager>,
/// KWin blur manager.
pub kwin_blur_manager: Option<KWinBlurManager>,
/// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>,
@@ -132,7 +136,7 @@ impl WinitState {
let seat_state = SeatState::new(globals, queue_handle);
let mut seats = FnvHashMap::default();
let mut seats = AHashMap::default();
for seat in seat_state.seats() {
seats.insert(seat.id(), WinitSeatState::new());
}
@@ -161,6 +165,7 @@ impl WinitState {
window_events_sink: Default::default(),
viewporter_state,
fractional_scaling_manager,
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(),
seats,
text_input_state: TextInputState::new(globals, queue_handle).ok(),
@@ -282,7 +287,12 @@ impl WindowHandler for WinitState {
.expect("got configure for dead window.")
.lock()
.unwrap()
.configure(configure, &self.shm, &self.subcompositor_state);
.configure(
configure,
&self.shm,
&self.subcompositor_state,
&mut self.events_sink,
);
self.window_compositor_updates[pos].size = Some(new_size);
}
@@ -320,6 +330,16 @@ impl OutputHandler for WinitState {
}
impl CompositorHandler for WinitState {
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wayland_client::protocol::wl_surface::WlSurface,
_: wayland_client::protocol::wl_output::Transform,
) {
// TODO(kchibisov) we need to expose it somehow in winit.
}
fn scale_factor_changed(
&mut self,
_: &Connection,

View File

@@ -0,0 +1,70 @@
//! Handling of KDE-compatible blur.
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
use wayland_protocols_plasma::blur::client::{
org_kde_kwin_blur::OrgKdeKwinBlur, org_kde_kwin_blur_manager::OrgKdeKwinBlurManager,
};
use sctk::globals::GlobalData;
use crate::platform_impl::wayland::state::WinitState;
/// KWin blur manager.
#[derive(Debug, Clone)]
pub struct KWinBlurManager {
manager: OrgKdeKwinBlurManager,
}
impl KWinBlurManager {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { manager })
}
pub fn blur(
&self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> OrgKdeKwinBlur {
self.manager.create(surface, queue_handle, ())
}
pub fn unset(&self, surface: &WlSurface) {
self.manager.unset(surface)
}
}
impl Dispatch<OrgKdeKwinBlurManager, GlobalData, WinitState> for KWinBlurManager {
fn event(
_: &mut WinitState,
_: &OrgKdeKwinBlurManager,
_: <OrgKdeKwinBlurManager as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
unreachable!("no events defined for org_kde_kwin_blur_manager");
}
}
impl Dispatch<OrgKdeKwinBlur, (), WinitState> for KWinBlurManager {
fn event(
_: &mut WinitState,
_: &OrgKdeKwinBlur,
_: <OrgKdeKwinBlur as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
unreachable!("no events defined for org_kde_kwin_blur");
}
}
delegate_dispatch!(WinitState: [OrgKdeKwinBlurManager: GlobalData] => KWinBlurManager);
delegate_dispatch!(WinitState: [OrgKdeKwinBlur: ()] => KWinBlurManager);

View File

@@ -1,5 +1,6 @@
//! Wayland protocol implementation boilerplate.
pub mod kwin_blur;
pub mod wp_fractional_scaling;
pub mod wp_viewporter;
pub mod xdg_activation;

View File

@@ -3,10 +3,6 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use raw_window_handle::{
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
};
use sctk::reexports::calloop;
use sctk::reexports::client::protocol::wl_display::WlDisplay;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
@@ -24,12 +20,12 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons,
WindowAttributes, WindowButtons, WindowLevel,
};
use super::event_loop::sink::EventSink;
@@ -57,6 +53,7 @@ pub struct Window {
compositor: Arc<CompositorState>,
/// The wayland display used solely for raw window handle.
#[allow(dead_code)]
display: WlDisplay,
/// Xdg activation to request user attention.
@@ -131,6 +128,8 @@ impl Window {
// Set transparency hint.
window_state.set_transparent(attributes.transparent);
window_state.set_blur(attributes.blur);
// Set the decorations hint.
window_state.set_decorate(attributes.decorations);
@@ -375,6 +374,13 @@ impl Window {
None
}
#[inline]
pub fn show_window_menu(&self, position: Position) {
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
self.window_state.lock().unwrap().show_window_menu(position);
}
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window_state
@@ -409,6 +415,11 @@ impl Window {
self.window_state.lock().unwrap().scale_factor()
}
#[inline]
pub fn set_blur(&self, blur: bool) {
self.window_state.lock().unwrap().set_blur(blur);
}
#[inline]
pub fn set_decorations(&self, decorate: bool) {
self.window_state.lock().unwrap().set_decorate(decorate)
@@ -419,6 +430,12 @@ impl Window {
self.window_state.lock().unwrap().is_decorated()
}
#[inline]
pub fn set_window_level(&self, _level: WindowLevel) {}
#[inline]
pub(crate) fn set_window_icon(&self, _window_icon: Option<PlatformIcon>) {}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
// You can't unminimize the window on Wayland.
@@ -612,6 +629,9 @@ impl Window {
self.window_state.lock().unwrap().set_ime_purpose(purpose);
}
#[inline]
pub fn focus_window(&self) {}
#[inline]
pub fn surface(&self) -> &WlSurface {
self.window.wl_surface()
@@ -629,23 +649,56 @@ impl Window {
}
#[inline]
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
// XXX there's no such concept on Wayland.
None
}
#[cfg(feature = "rwh_04")]
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = WaylandWindowHandle::empty();
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::WaylandHandle::empty();
window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _;
RawWindowHandle::Wayland(window_handle)
window_handle.display = self.display.id().as_ptr() as *mut _;
rwh_04::RawWindowHandle::Wayland(window_handle)
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::WaylandWindowHandle::empty();
window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _;
rwh_05::RawWindowHandle::Wayland(window_handle)
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
display_handle.display = self.display.id().as_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
rwh_05::RawDisplayHandle::Wayland(display_handle)
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
Ok(rwh_06::WaylandWindowHandle::new({
let ptr = self.window.wl_surface().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
})
.into())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::WaylandDisplayHandle::new({
let ptr = self.display.id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
})
.into())
}
#[inline]
@@ -658,6 +711,8 @@ impl Window {
self.window_state.lock().unwrap().theme()
}
pub fn set_content_protected(&self, _protected: bool) {}
#[inline]
pub fn title(&self) -> String {
self.window_state.lock().unwrap().title().to_owned()

View File

@@ -3,29 +3,37 @@
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use std::sync::{Arc, Weak};
use std::time::Duration;
use log::warn;
use log::{info, warn};
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::csd_frame::{
DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState,
};
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge;
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
use sctk::compositor::{CompositorState, Region, SurfaceData};
use sctk::compositor::{CompositorState, Region};
use sctk::seat::pointer::ThemedPointer;
use sctk::shell::xdg::frame::{DecorationsFrame, FrameAction, FrameClick};
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
use sctk::shell::xdg::XdgSurface;
use sctk::shell::WaylandSurface;
use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::{ExternalError, NotSupportedError};
use crate::event::WindowEvent;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::make_wid;
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
@@ -37,7 +45,7 @@ use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
#[cfg(feature = "sctk-adwaita")]
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
#[cfg(not(feature = "sctk-adwaita"))]
pub type WinitFrame = sctk::shell::xdg::frame::fallback_frame::FallbackFrame<WinitState>;
pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState>;
// Minimum window inner size.
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
@@ -130,6 +138,8 @@ pub struct WindowState {
viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>,
blur: Option<OrgKdeKwinBlur>,
blur_manager: Option<KWinBlurManager>,
/// Whether the client side decorations have pending move operations.
///
@@ -159,6 +169,8 @@ impl WindowState {
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
Self {
blur: None,
blur_manager: winit_state.kwin_blur_manager.clone(),
compositor,
connection,
csd_fails: false,
@@ -239,6 +251,7 @@ impl WindowState {
configure: WindowConfigure,
shm: &Shm,
subcompositor: &Arc<SubcompositorState>,
event_sink: &mut EventSink,
) -> LogicalSize<u32> {
if configure.decoration_mode == DecorationMode::Client
&& self.frame.is_none()
@@ -254,6 +267,7 @@ impl WindowState {
) {
Ok(mut frame) => {
frame.set_title(&self.title);
frame.set_scaling_factor(self.scale_factor);
// Hide the frame if we were asked to not decorate.
frame.set_hidden(!self.decorate);
self.frame = Some(frame);
@@ -270,6 +284,19 @@ impl WindowState {
let stateless = Self::is_stateless(&configure);
// Emit `Occluded` event on suspension change.
let occluded = configure.state.contains(XdgWindowState::SUSPENDED);
if self
.last_configure
.as_ref()
.map(|c| c.state.contains(XdgWindowState::SUSPENDED))
.unwrap_or(false)
!= occluded
{
let window_id = make_wid(self.window.wl_surface());
event_sink.push_window_event(WindowEvent::Occluded(occluded), window_id);
}
let new_size = if let Some(frame) = self.frame.as_mut() {
// Configure the window states.
frame.update_state(configure.state);
@@ -336,23 +363,40 @@ impl WindowState {
}
/// Tells whether the window should be closed.
#[allow(clippy::too_many_arguments)]
pub fn frame_click(
&mut self,
click: FrameClick,
pressed: bool,
seat: &WlSeat,
serial: u32,
timestamp: Duration,
window_id: WindowId,
updates: &mut Vec<WindowCompositorUpdate>,
) -> Option<bool> {
match self.frame.as_mut()?.on_click(click, pressed)? {
match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
FrameAction::Minimize => self.window.set_minimized(),
FrameAction::Maximize => self.window.set_maximized(),
FrameAction::UnMaximize => self.window.unset_maximized(),
FrameAction::Close => WinitState::queue_close(updates, window_id),
FrameAction::Move => self.has_pending_move = Some(serial),
FrameAction::Resize(edge) => self.window.resize(seat, serial, edge),
FrameAction::Resize(edge) => {
let edge = match edge {
ResizeEdge::None => XdgResizeEdge::None,
ResizeEdge::Top => XdgResizeEdge::Top,
ResizeEdge::Bottom => XdgResizeEdge::Bottom,
ResizeEdge::Left => XdgResizeEdge::Left,
ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
ResizeEdge::Right => XdgResizeEdge::Right,
ResizeEdge::TopRight => XdgResizeEdge::TopRight,
ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
_ => return None,
};
self.window.resize(seat, serial, edge);
}
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
_ => (),
};
Some(false)
@@ -369,14 +413,15 @@ impl WindowState {
&mut self,
seat: &WlSeat,
surface: &WlSurface,
timestamp: Duration,
x: f64,
y: f64,
) -> Option<&str> {
) -> Option<CursorIcon> {
// Take the serial if we had any, so it doesn't stick around.
let serial = self.has_pending_move.take();
if let Some(frame) = self.frame.as_mut() {
let cursor = frame.click_point_moved(surface, x, y);
let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
// If we have a cursor change, that means that cursor is over the decorations,
// so try to apply move.
if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
@@ -492,14 +537,12 @@ impl WindowState {
/// Refresh the decorations frame if it's present returning whether the client should redraw.
pub fn refresh_frame(&mut self) -> bool {
if let Some(frame) = self.frame.as_mut() {
let dirty = frame.is_dirty();
if dirty {
frame.draw();
if frame.is_dirty() {
return frame.draw();
}
dirty
} else {
false
}
false
}
/// Reload the cursor style on the given window.
@@ -586,20 +629,8 @@ impl WindowState {
return;
}
self.apply_on_poiner(|pointer, data| {
let surface = data.cursor_surface();
let scale_factor = surface.data::<SurfaceData>().unwrap().scale_factor();
if pointer
.set_cursor(
&self.connection,
cursor_icon.name(),
&self.shm,
surface,
scale_factor,
)
.is_err()
{
self.apply_on_poiner(|pointer, _| {
if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
warn!("Failed to set cursor to {:?}", cursor_icon);
}
})
@@ -703,6 +734,15 @@ impl WindowState {
Ok(())
}
pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
// TODO(kchibisov) handle touch serials.
self.apply_on_poiner(|_, data| {
let serial = data.latest_button_serial();
let seat = data.seat();
self.window.show_window_menu(seat, serial, position.into());
});
}
/// Set the position of the cursor.
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
if self.pointer_constraints.is_none() {
@@ -838,6 +878,30 @@ impl WindowState {
if self.fractional_scale.is_none() {
let _ = self.window.set_buffer_scale(self.scale_factor as _);
}
if let Some(frame) = self.frame.as_mut() {
frame.set_scaling_factor(scale_factor);
}
}
/// Make window background blurred
#[inline]
pub fn set_blur(&mut self, blurred: bool) {
if blurred && self.blur.is_none() {
if let Some(blur_manager) = self.blur_manager.as_ref() {
let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
blur.commit();
self.blur = Some(blur);
} else {
info!("Blur manager unavailable, unable to change blur")
}
} else if !blurred && self.blur.is_some() {
self.blur_manager
.as_ref()
.unwrap()
.unset(self.window.wl_surface());
self.blur.take().unwrap().release();
}
}
/// Set the window title to a new value.
@@ -900,6 +964,20 @@ impl Drop for WindowState {
ManuallyDrop::drop(&mut self.window);
}
// Cleanup objects.
if let Some(blur) = self.blur.take() {
blur.release();
}
if let Some(fs) = self.fractional_scale.take() {
fs.destroy();
}
if let Some(viewport) = self.viewport.take() {
viewport.destroy();
}
surface.destroy();
}
}
@@ -935,17 +1013,17 @@ pub enum FrameCallbackState {
Received,
}
impl From<ResizeDirection> for ResizeEdge {
impl From<ResizeDirection> for XdgResizeEdge {
fn from(value: ResizeDirection) -> Self {
match value {
ResizeDirection::North => ResizeEdge::Top,
ResizeDirection::West => ResizeEdge::Left,
ResizeDirection::NorthWest => ResizeEdge::TopLeft,
ResizeDirection::NorthEast => ResizeEdge::TopRight,
ResizeDirection::East => ResizeEdge::Right,
ResizeDirection::SouthWest => ResizeEdge::BottomLeft,
ResizeDirection::SouthEast => ResizeEdge::BottomRight,
ResizeDirection::South => ResizeEdge::Bottom,
ResizeDirection::North => XdgResizeEdge::Top,
ResizeDirection::West => XdgResizeEdge::Left,
ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
ResizeDirection::East => XdgResizeEdge::Right,
ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
ResizeDirection::South => XdgResizeEdge::Bottom,
}
}
}

View File

@@ -7,13 +7,18 @@ use std::{
sync::{Arc, Mutex},
};
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::Serialize;
use x11rb::{
protocol::{
xinput,
xproto::{self, ConnectionExt as _},
},
x11_utils::ExtensionInformation,
};
use super::{
atoms::*, ffi, get_xtarget, mkdid, mkwid, monitor, util, CookieResultExt, Device, DeviceId,
DeviceInfo, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow,
WindowId, XExtension,
atoms::*, ffi, get_xtarget, mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo,
Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
};
use crate::{
@@ -35,10 +40,10 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) dnd: Dnd,
pub(super) ime_receiver: ImeReceiver,
pub(super) ime_event_receiver: ImeEventReceiver,
pub(super) randr_event_offset: c_int,
pub(super) randr_event_offset: u8,
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension,
pub(super) xkbext: XExtension,
pub(super) xi2ext: ExtensionInformation,
pub(super) xkbext: ExtensionInformation,
pub(super) target: Rc<RootELW<T>>,
pub(super) kb_state: KbdState,
// Number of touch events currently in progress
@@ -55,12 +60,12 @@ pub(super) struct EventProcessor<T: 'static> {
}
impl<T: 'static> EventProcessor<T> {
pub(super) fn init_device(&self, device: c_int) {
pub(super) fn init_device(&self, device: xinput::DeviceId) {
let wt = get_xtarget(&self.target);
let mut devices = self.devices.borrow_mut();
if let Some(info) = DeviceInfo::get(&wt.xconn, device) {
if let Some(info) = DeviceInfo::get(&wt.xconn, device as _) {
for info in info.iter() {
devices.insert(DeviceId(info.deviceid), Device::new(info));
devices.insert(DeviceId(info.deviceid as _), Device::new(info));
}
}
}
@@ -117,12 +122,14 @@ impl<T: 'static> EventProcessor<T> {
1
}
let result = (wt.xconn.xlib.XCheckIfEvent)(
wt.xconn.display,
event_ptr,
Some(predicate),
std::ptr::null_mut(),
);
let result = unsafe {
(wt.xconn.xlib.XCheckIfEvent)(
wt.xconn.display,
event_ptr,
Some(predicate),
std::ptr::null_mut(),
)
};
result != 0
}
@@ -420,7 +427,10 @@ impl<T: 'static> EventProcessor<T> {
let last_scale_factor = shared_state_lock.last_monitor.scale_factor;
let new_scale_factor = {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
let monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
let monitor = wt
.xconn
.get_monitor_for_window(Some(window_rect))
.expect("Failed to find monitor for window");
if monitor.is_dummy() {
// Avoid updating monitor using a dummy monitor handle
@@ -576,7 +586,10 @@ impl<T: 'static> EventProcessor<T> {
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
callback(Event::RedrawRequested(window_id));
callback(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
}
}
@@ -593,7 +606,7 @@ impl<T: 'static> EventProcessor<T> {
};
let window_id = mkwid(window);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let keycode = xkev.keycode as _;
@@ -669,7 +682,7 @@ impl<T: 'static> EventProcessor<T> {
return;
};
let xev = &guard.cookie;
if self.xi2ext.opcode != xev.extension {
if self.xi2ext.major_opcode != xev.extension as u8 {
return;
}
@@ -688,7 +701,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event as xproto::Window);
let device_id = mkdid(xev.deviceid);
let device_id = mkdid(xev.deviceid as xinput::DeviceId);
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
@@ -732,7 +745,7 @@ impl<T: 'static> EventProcessor<T> {
// Suppress emulated scroll wheel clicks, since we handle the real motion events for those.
// In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in
// turn) as axis motion, so we don't otherwise special-case these button presses.
4 | 5 | 6 | 7 => {
4..=7 => {
if xev.flags & ffi::XIPointerEmulated == 0 {
callback(Event::WindowEvent {
window_id,
@@ -784,7 +797,7 @@ impl<T: 'static> EventProcessor<T> {
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let device_id = mkdid(xev.deviceid);
let device_id = mkdid(xev.deviceid as xinput::DeviceId);
let window = xev.event as xproto::Window;
let window_id = mkwid(window);
let new_cursor_pos = (xev.event_x, xev.event_y);
@@ -817,7 +830,9 @@ impl<T: 'static> EventProcessor<T> {
)
};
let mut devices = self.devices.borrow_mut();
let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) {
let physical_device = match devices
.get_mut(&DeviceId(xev.sourceid as xinput::DeviceId))
{
Some(device) => device,
None => return,
};
@@ -829,7 +844,7 @@ impl<T: 'static> EventProcessor<T> {
if let Some(&mut (_, ref mut info)) = physical_device
.scroll_axes
.iter_mut()
.find(|&&mut (axis, _)| axis == i)
.find(|&&mut (axis, _)| axis == i as _)
{
let delta = (x - info.position) / info.increment;
info.position = x;
@@ -876,9 +891,11 @@ impl<T: 'static> EventProcessor<T> {
let window = xev.event as xproto::Window;
let window_id = mkwid(window);
let device_id = mkdid(xev.deviceid);
let device_id = mkdid(xev.deviceid as xinput::DeviceId);
if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) {
if let Some(all_info) =
DeviceInfo::get(&wt.xconn, super::ALL_DEVICES.into())
{
let mut devices = self.devices.borrow_mut();
for device_info in all_info.iter() {
if device_info.deviceid == xev.sourceid
@@ -888,7 +905,7 @@ impl<T: 'static> EventProcessor<T> {
// the virtual device.
|| device_info.attachment == xev.sourceid
{
let device_id = DeviceId(device_info.deviceid);
let device_id = DeviceId(device_info.deviceid as _);
if let Some(device) = devices.get_mut(&device_id) {
device.reset_scroll_position(device_info);
}
@@ -927,7 +944,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent {
window_id: mkwid(window),
event: CursorLeft {
device_id: mkdid(xev.deviceid),
device_id: mkdid(xev.deviceid as xinput::DeviceId),
},
});
}
@@ -975,14 +992,14 @@ impl<T: 'static> EventProcessor<T> {
let pointer_id = self
.devices
.borrow()
.get(&DeviceId(xev.deviceid))
.get(&DeviceId(xev.deviceid as xinput::DeviceId))
.map(|device| device.attachment)
.unwrap_or(2);
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
device_id: mkdid(pointer_id as _),
position,
},
});
@@ -1073,7 +1090,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved {
device_id: mkdid(util::VIRTUAL_CORE_POINTER.into()),
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
position: location.cast(),
},
});
@@ -1082,7 +1099,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
device_id: mkdid(xev.deviceid),
device_id: mkdid(xev.deviceid as xinput::DeviceId),
phase,
location,
force: None, // TODO
@@ -1100,7 +1117,7 @@ impl<T: 'static> EventProcessor<T> {
if xev.flags & ffi::XIPointerEmulated == 0 {
callback(Event::DeviceEvent {
device_id: mkdid(xev.deviceid),
device_id: mkdid(xev.deviceid as xinput::DeviceId),
event: DeviceEvent::Button {
button: xev.detail as u32,
state: match xev.evtype {
@@ -1119,7 +1136,7 @@ impl<T: 'static> EventProcessor<T> {
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let did = mkdid(xev.deviceid);
let did = mkdid(xev.deviceid as xinput::DeviceId);
let mask = unsafe {
slice::from_raw_parts(
@@ -1179,7 +1196,7 @@ impl<T: 'static> EventProcessor<T> {
_ => unreachable!(),
};
let device_id = mkdid(xev.sourceid);
let device_id = mkdid(xev.sourceid as xinput::DeviceId);
let keycode = xev.detail as u32;
if keycode < KEYCODE_OFFSET as u32 {
return;
@@ -1205,19 +1222,19 @@ impl<T: 'static> EventProcessor<T> {
unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }
{
if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) {
self.init_device(info.deviceid);
self.init_device(info.deviceid as xinput::DeviceId);
callback(Event::DeviceEvent {
device_id: mkdid(info.deviceid),
device_id: mkdid(info.deviceid as xinput::DeviceId),
event: DeviceEvent::Added,
});
} else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved)
{
callback(Event::DeviceEvent {
device_id: mkdid(info.deviceid),
device_id: mkdid(info.deviceid as xinput::DeviceId),
event: DeviceEvent::Removed,
});
let mut devices = self.devices.borrow_mut();
devices.remove(&DeviceId(info.deviceid));
devices.remove(&DeviceId(info.deviceid as xinput::DeviceId));
}
}
}
@@ -1226,7 +1243,7 @@ impl<T: 'static> EventProcessor<T> {
}
}
_ => {
if event_type == self.xkbext.first_event_id {
if event_type == self.xkbext.first_event as _ {
let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) };
match xev.xkb_type {
ffi::XkbNewKeyboardNotify => {
@@ -1282,11 +1299,14 @@ impl<T: 'static> EventProcessor<T> {
_ => {}
}
}
if event_type == self.randr_event_offset {
if event_type == self.randr_event_offset as c_int {
// In the future, it would be quite easy to emit monitor hotplug events.
let prev_list = monitor::invalidate_cached_monitor_list();
let prev_list = wt.xconn.invalidate_cached_monitor_list();
if let Some(prev_list) = prev_list {
let new_list = wt.xconn.available_monitors();
let new_list = wt
.xconn
.available_monitors()
.expect("Failed to get monitor list");
for new_monitor in new_list {
// Previous list may be empty, in case of disconnecting and
// reconnecting the only one monitor. We still need to emit events in
@@ -1299,7 +1319,8 @@ impl<T: 'static> EventProcessor<T> {
for (window_id, window) in wt.windows.borrow().iter() {
if let Some(window) = window.upgrade() {
// Check if the window is on this monitor
let monitor = window.current_monitor();
let monitor =
window.shared_state_lock().last_monitor.clone();
if monitor.name == new_monitor.name {
let (width, height) = window.inner_size_physical();
let (new_width, new_height) = window.adjust_for_dpi(
@@ -1416,7 +1437,7 @@ impl<T: 'static> EventProcessor<T> {
) where
F: FnMut(Event<T>),
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
// Update modifiers state and emit key events based on which keys are currently pressed.
for keycode in wt

View File

@@ -1,7 +1,6 @@
use x11_dl::xmd::CARD32;
pub use x11_dl::{
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
xrandr::*, xrender::*,
};
// Isn't defined by x11_dl

View File

@@ -16,7 +16,7 @@ pub(crate) unsafe fn xim_set_callback(
) -> Result<(), XError> {
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
// access that isn't type-checked.
(xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>());
unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) };
xconn.check_errors()
}
@@ -30,14 +30,16 @@ pub(crate) unsafe fn set_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
(xconn.xlib.XRegisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
);
unsafe {
(xconn.xlib.XRegisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
)
};
xconn.check_errors()
}
@@ -45,14 +47,16 @@ pub(crate) unsafe fn unset_instantiate_callback(
xconn: &Arc<XConnection>,
client_data: ffi::XPointer,
) -> Result<(), XError> {
(xconn.xlib.XUnregisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
);
unsafe {
(xconn.xlib.XUnregisterIMInstantiateCallback)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
Some(xim_instantiate_callback),
client_data,
)
};
xconn.check_errors()
}
@@ -61,12 +65,14 @@ pub(crate) unsafe fn set_destroy_callback(
im: ffi::XIM,
inner: &ImeInner,
) -> Result<(), XError> {
xim_set_callback(
xconn,
im,
ffi::XNDestroyCallback_0.as_ptr() as *const _,
&inner.destroy_callback as *const _ as *mut _,
)
unsafe {
xim_set_callback(
xconn,
im,
ffi::XNDestroyCallback_0.as_ptr() as *const _,
&inner.destroy_callback as *const _ as *mut _,
)
}
}
#[derive(Debug)]
@@ -82,14 +88,16 @@ enum ReplaceImError {
// includes replacing all existing input contexts and free'ing resources as necessary. This only
// modifies existing state if all operations succeed.
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let xconn = &(*inner).xconn;
let xconn = unsafe { &(*inner).xconn };
let (new_im, is_fallback) = {
let new_im = (*inner).potential_input_methods.open_im(xconn, None);
let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
let is_fallback = new_im.is_fallback();
(
new_im.ok().ok_or_else(|| {
ReplaceImError::MethodOpenFailed(Box::new((*inner).potential_input_methods.clone()))
ReplaceImError::MethodOpenFailed(Box::new(unsafe {
(*inner).potential_input_methods.clone()
}))
})?,
is_fallback,
)
@@ -98,16 +106,16 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
// It's important to always set a destroy callback, since there's otherwise potential for us
// to try to use or free a resource that's already been destroyed on the server.
{
let result = set_destroy_callback(xconn, new_im.im, &*inner);
let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
if result.is_err() {
let _ = close_im(xconn, new_im.im);
let _ = unsafe { close_im(xconn, new_im.im) };
}
result
}
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
let mut new_contexts = HashMap::new();
for (window, old_context) in (*inner).contexts.iter() {
for (window, old_context) in unsafe { (*inner).contexts.iter() } {
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
// Check if the IME was allowed on that context.
@@ -125,16 +133,18 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
};
let new_context = {
let result = ImeContext::new(
xconn,
new_im.im,
style,
*window,
spot,
(*inner).event_sender.clone(),
);
let result = unsafe {
ImeContext::new(
xconn,
new_im.im,
style,
*window,
spot,
(*inner).event_sender.clone(),
)
};
if result.is_err() {
let _ = close_im(xconn, new_im.im);
let _ = unsafe { close_im(xconn, new_im.im) };
}
result.map_err(ReplaceImError::ContextCreationFailed)?
};
@@ -142,12 +152,14 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
}
// If we've made it this far, everything succeeded.
let _ = (*inner).destroy_all_contexts_if_necessary();
let _ = (*inner).close_im_if_necessary();
(*inner).im = Some(new_im);
(*inner).contexts = new_contexts;
(*inner).is_destroyed = false;
(*inner).is_fallback = is_fallback;
unsafe {
let _ = (*inner).destroy_all_contexts_if_necessary();
let _ = (*inner).close_im_if_necessary();
(*inner).im = Some(new_im);
(*inner).contexts = new_contexts;
(*inner).is_destroyed = false;
(*inner).is_fallback = is_fallback;
}
Ok(())
}
@@ -159,18 +171,18 @@ pub unsafe extern "C" fn xim_instantiate_callback(
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
let xconn = &(*inner).xconn;
match replace_im(inner) {
Ok(()) => {
let xconn = unsafe { &(*inner).xconn };
match unsafe { replace_im(inner) } {
Ok(()) => unsafe {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
}
Err(err) => {
},
Err(err) => unsafe {
if (*inner).is_destroyed {
// We have no usable input methods!
panic!("Failed to reopen input method: {err:?}");
}
}
},
}
}
}
@@ -186,13 +198,13 @@ pub unsafe extern "C" fn xim_destroy_callback(
) {
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
(*inner).is_destroyed = true;
let xconn = &(*inner).xconn;
if !(*inner).is_fallback {
let _ = set_instantiate_callback(xconn, client_data);
unsafe { (*inner).is_destroyed = true };
let xconn = unsafe { &(*inner).xconn };
if unsafe { !(*inner).is_fallback } {
let _ = unsafe { set_instantiate_callback(xconn, client_data) };
// Attempt to open fallback input method.
match replace_im(inner) {
Ok(()) => (*inner).is_fallback = true,
match unsafe { replace_im(inner) } {
Ok(()) => unsafe { (*inner).is_fallback = true },
Err(err) => {
// We have no usable input methods!
panic!("Failed to open fallback input method: {err:?}");

View File

@@ -217,15 +217,19 @@ impl ImeContext {
}));
let ic = match style as _ {
Style::Preedit(style) => ImeContext::create_preedit_ic(
xconn,
im,
style,
window,
client_data as ffi::XPointer,
),
Style::Nothing(style) => ImeContext::create_nothing_ic(xconn, im, style, window),
Style::None(style) => ImeContext::create_none_ic(xconn, im, style, window),
Style::Preedit(style) => unsafe {
ImeContext::create_preedit_ic(
xconn,
im,
style,
window,
client_data as ffi::XPointer,
)
},
Style::Nothing(style) => unsafe {
ImeContext::create_nothing_ic(xconn, im, style, window)
},
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
}
.ok_or(ImeContextCreationError::Null)?;
@@ -237,7 +241,7 @@ impl ImeContext {
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
style,
_client_data: Box::from_raw(client_data),
_client_data: unsafe { Box::from_raw(client_data) },
};
// Set the spot location, if it's present.
@@ -254,14 +258,16 @@ impl ImeContext {
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
);
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
@@ -274,8 +280,7 @@ impl ImeContext {
client_data: ffi::XPointer,
) -> Option<ffi::XIC> {
let preedit_callbacks = PreeditCallbacks::new(client_data);
let preedit_attr = util::memory::XSmartPointer::new(
xconn,
let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
@@ -287,20 +292,22 @@ impl ImeContext {
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
&(preedit_callbacks.draw_callback) as *const _,
ptr::null_mut::<()>(),
),
)
)
})
.expect("XVaCreateNestedList returned NULL");
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
preedit_attr.ptr,
ptr::null_mut::<()>(),
);
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
preedit_attr.ptr,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}
@@ -311,14 +318,16 @@ impl ImeContext {
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
);
let ic = unsafe {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
)
};
(!ic.is_null()).then_some(ic)
}

View File

@@ -9,12 +9,12 @@ use super::{
use crate::platform_impl::platform::x11::ime::ImeEventSender;
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
(xconn.xlib.XCloseIM)(im);
unsafe { (xconn.xlib.XCloseIM)(im) };
xconn.check_errors()
}
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
(xconn.xlib.XDestroyIC)(ic);
unsafe { (xconn.xlib.XDestroyIC)(ic) };
xconn.check_errors()
}
@@ -52,7 +52,7 @@ impl ImeInner {
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
if !self.is_destroyed && self.im.is_some() {
close_im(&self.xconn, self.im.as_ref().unwrap().im).map(|_| true)
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
} else {
Ok(false)
}
@@ -60,7 +60,7 @@ impl ImeInner {
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
if !self.is_destroyed {
destroy_ic(&self.xconn, ic).map(|_| true)
unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true)
} else {
Ok(false)
}
@@ -68,7 +68,7 @@ impl ImeInner {
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
for context in self.contexts.values().flatten() {
self.destroy_ic_if_necessary(context.ic)?;
unsafe { self.destroy_ic_if_necessary(context.ic)? };
}
Ok(!self.is_destroyed)
}

View File

@@ -21,14 +21,16 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
// * The new locale modifiers if we succeeded in setting them.
// * NULL if the locale modifiers string is malformed or if the
// current locale is not supported by Xlib.
(xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr());
unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
let im = (xconn.xlib.XOpenIM)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
);
let im = unsafe {
(xconn.xlib.XOpenIM)(
xconn.display,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
if im.is_null() {
None
@@ -178,7 +180,7 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
let atoms = xconn.atoms();
let servers_atom = atoms[XIM_SERVERS];
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
let mut atoms: Vec<ffi::Atom> = xconn
.get_property::<xproto::Atom>(
@@ -192,21 +194,23 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
.collect::<Vec<_>>();
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
(xconn.xlib.XGetAtomNames)(
xconn.display,
atoms.as_mut_ptr(),
atoms.len() as _,
names.as_mut_ptr() as _,
);
names.set_len(atoms.len());
unsafe {
(xconn.xlib.XGetAtomNames)(
xconn.display,
atoms.as_mut_ptr(),
atoms.len() as _,
names.as_mut_ptr() as _,
)
};
unsafe { names.set_len(atoms.len()) };
let mut formatted_names = Vec::with_capacity(names.len());
for name in names {
let string = CStr::from_ptr(name)
let string = unsafe { CStr::from_ptr(name) }
.to_owned()
.into_string()
.map_err(GetXimServersError::InvalidUtf8)?;
(xconn.xlib.XFree)(name as _);
unsafe { (xconn.xlib.XFree)(name as _) };
formatted_names.push(string.replace("@server=", "@im="));
}
xconn.check_errors().map_err(GetXimServersError::XError)?;

View File

@@ -28,15 +28,15 @@ use std::{
collections::{HashMap, HashSet},
ffi::CStr,
fmt,
mem::{self, MaybeUninit},
mem::MaybeUninit,
ops::Deref,
os::{
raw::*,
unix::io::{AsRawFd, RawFd},
unix::io::{AsRawFd, BorrowedFd},
},
ptr,
rc::Rc,
slice,
slice, str,
sync::mpsc::{Receiver, Sender, TryRecvError},
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
@@ -45,13 +45,16 @@ use std::{
use libc::{self, setlocale, LC_CTYPE};
use atoms::*;
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
use x11rb::protocol::{
xinput,
xproto::{self, ConnectionExt},
};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::{
connection::RequestConnection,
protocol::{
xinput::{self, ConnectionExt as _},
xkb,
xproto::{self, ConnectionExt as _},
},
};
use x11rb::{
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
xcb_ffi::ReplyOrIdError,
@@ -62,20 +65,24 @@ use self::{
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
};
use super::{common::xkb_state::KbdState, OsError};
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
use crate::{
error::{EventLoopError, OsError as RootOsError},
event::{Event, StartCause},
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
event::{Event, StartCause, WindowEvent},
event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform::pump_events::PumpStatus,
platform_impl::{
platform::{min_timeout, sticky_exit_callback, WindowId},
platform::{min_timeout, WindowId},
PlatformSpecificWindowBuilderAttributes,
},
window::WindowAttributes,
};
type X11Source = Generic<RawFd>;
// Xinput constants not defined in x11rb
const ALL_DEVICES: u16 = 0;
const ALL_MASTER_DEVICES: u16 = 1;
type X11Source = Generic<BorrowedFd<'static>>;
struct WakeSender<T> {
sender: Sender<T>,
@@ -140,6 +147,8 @@ pub struct EventLoopWindowTarget<T> {
wm_delete_window: xproto::Atom,
net_wm_ping: xproto::Atom,
ime_sender: ImeSender,
control_flow: Cell<ControlFlow>,
exit: Cell<Option<i32>>,
root: xproto::Window,
ime: RefCell<Ime>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
@@ -151,7 +160,6 @@ pub struct EventLoopWindowTarget<T> {
pub struct EventLoop<T: 'static> {
loop_running: bool,
control_flow: ControlFlow,
event_loop: Loop<'static, EventLoopState>,
waker: calloop::ping::Ping,
event_processor: EventProcessor<T>,
@@ -229,71 +237,27 @@ impl<T: 'static> EventLoop<T> {
});
let randr_event_offset = xconn
.select_xrandr_input(root as ffi::Window)
.select_xrandr_input(root)
.expect("Failed to query XRandR extension");
let xi2ext = unsafe {
let mut ext = XExtension::default();
let xi2ext = xconn
.xcb_connection()
.extension_information(xinput::X11_EXTENSION_NAME)
.expect("Failed to query XInput extension")
.expect("X server missing XInput extension");
let xkbext = xconn
.xcb_connection()
.extension_information(xkb::X11_EXTENSION_NAME)
.expect("Failed to query XKB extension")
.expect("X server missing XKB extension");
let res = (xconn.xlib.XQueryExtension)(
xconn.display,
b"XInputExtension\0".as_ptr() as *const c_char,
&mut ext.opcode,
&mut ext.first_event_id,
&mut ext.first_error_id,
);
if res == ffi::False {
panic!("X server missing XInput extension");
}
ext
};
let xkbext = {
let mut ext = XExtension::default();
let res = unsafe {
(xconn.xlib.XkbQueryExtension)(
xconn.display,
&mut ext.opcode,
&mut ext.first_event_id,
&mut ext.first_error_id,
&mut 1,
&mut 0,
)
};
if res == ffi::False {
panic!("X server missing XKB extension");
}
// Enable detectable auto repeat.
let mut supported = 0;
unsafe {
(xconn.xlib.XkbSetDetectableAutoRepeat)(xconn.display, 1, &mut supported);
}
if supported == 0 {
warn!("Detectable auto repeart is not supported");
}
ext
};
unsafe {
let mut xinput_major_ver = ffi::XI_2_Major;
let mut xinput_minor_ver = ffi::XI_2_Minor;
if (xconn.xinput2.XIQueryVersion)(
xconn.display,
&mut xinput_major_ver,
&mut xinput_minor_ver,
) != ffi::Success as std::os::raw::c_int
{
panic!(
"X server has XInput extension {xinput_major_ver}.{xinput_minor_ver} but does not support XInput2",
);
}
}
// Check for XInput2 support.
xconn
.xcb_connection()
.xinput_xi_query_version(2, 3)
.expect("Failed to send XInput2 query version request")
.reply()
.expect("Error while checking for XInput2 query version reply");
xconn.update_cached_wm_info(root);
@@ -304,7 +268,8 @@ impl<T: 'static> EventLoop<T> {
// Create the X11 event dispatcher.
let source = X11Source::new(
xconn.xcb_connection().as_raw_fd(),
// SAFETY: xcb owns the FD and outlives the source.
unsafe { BorrowedFd::borrow_raw(xconn.xcb_connection().as_raw_fd()) },
calloop::Interest::READ,
calloop::Mode::Level,
);
@@ -339,6 +304,8 @@ impl<T: 'static> EventLoop<T> {
let window_target = EventLoopWindowTarget {
ime,
root,
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
windows: Default::default(),
_marker: ::std::marker::PhantomData,
ime_sender,
@@ -387,7 +354,7 @@ impl<T: 'static> EventLoop<T> {
.xconn
.select_xinput_events(
root,
ffi::XIAllDevices as _,
ALL_DEVICES,
x11rb::protocol::xinput::XIEventMask::HIERARCHY,
)
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
@@ -396,15 +363,14 @@ impl<T: 'static> EventLoop<T> {
.xconn
.select_xkb_events(
0x100, // Use the "core keyboard device"
ffi::XkbNewKeyboardNotifyMask | ffi::XkbStateNotifyMask,
xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY,
)
.unwrap();
event_processor.init_device(ffi::XIAllDevices);
event_processor.init_device(ALL_DEVICES);
EventLoop {
loop_running: false,
control_flow: ControlFlow::default(),
event_loop,
waker,
event_processor,
@@ -432,9 +398,9 @@ impl<T: 'static> EventLoop<T> {
&self.target
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>),
{
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
@@ -455,7 +421,7 @@ impl<T: 'static> EventLoop<T> {
};
// Applications aren't allowed to carry windows between separate
// `run_ondemand` calls but if they have only just dropped their
// `run_on_demand` calls but if they have only just dropped their
// windows we need to make sure those last requests are sent to the
// X Server.
let wt = get_xtarget(&self.target);
@@ -468,35 +434,24 @@ impl<T: 'static> EventLoop<T> {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>),
{
if !self.loop_running {
self.loop_running = true;
// Reset the internal state for the loop as we start running to
// ensure consistent behaviour in case the loop runs and exits more
// than once.
self.control_flow = ControlFlow::Poll;
// run the initial loop iteration
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
}
if let ControlFlow::ExitWithCode(code) = self.control_flow {
if let Some(code) = self.exit_code() {
self.loop_running = false;
let mut dummy = self.control_flow;
sticky_exit_callback(
Event::LoopExiting,
self.window_target(),
&mut dummy,
&mut callback,
);
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
@@ -512,7 +467,7 @@ impl<T: 'static> EventLoop<T> {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>),
{
let start = Instant::now();
@@ -522,17 +477,12 @@ impl<T: 'static> EventLoop<T> {
// 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 {
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))
}
// This function shouldn't have to handle any requests to exit
// the application (there should be no need to poll for events
// if the application has requested to exit) so we consider
// it a bug in the backend if we ever see `ExitWithCode` here.
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
min_timeout(control_flow_timeout, timeout)
@@ -546,25 +496,13 @@ impl<T: 'static> EventLoop<T> {
{
log::error!("Failed to poll for events: {error:?}");
let exit_code = error.raw_os_error().unwrap_or(1);
self.control_flow = ControlFlow::ExitWithCode(exit_code);
return;
}
// False positive / spurious wake ups could lead to us spamming
// redundant iterations of the event loop with no new events to
// dispatch.
//
// If there's no readable event source then we just double check if we
// have any pending `_receiver` events and if not we return without
// running a loop iteration.
// If we don't have any pending `_receiver`
if !self.has_pending() && !self.state.x11_readiness.readable {
self.set_exit_code(exit_code);
return;
}
// NB: `StartCause::Init` is handled as a special case and doesn't need
// to be considered here
let cause = match self.control_flow {
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
@@ -583,42 +521,42 @@ impl<T: 'static> EventLoop<T> {
}
}
}
// This function shouldn't have to handle any requests to exit
// the application (there should be no need to poll for events
// if the application has requested to exit) so we consider
// it a bug in the backend if we ever see `ExitWithCode` here.
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
// False positive / spurious wake ups could lead to us spamming
// redundant iterations of the event loop with no new events to
// dispatch.
//
// If there's no readable event source then we just double check if we
// have any pending `_receiver` events and if not we return without
// running a loop iteration.
// If we don't have any pending `_receiver`
if !self.has_pending()
&& !matches!(
&cause,
StartCause::ResumeTimeReached { .. } | StartCause::Poll
)
{
return;
}
self.single_iteration(&mut callback, cause);
}
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>),
{
let mut control_flow = self.control_flow;
sticky_exit_callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
callback,
);
callback(crate::event::Event::NewEvents(cause), &self.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 {
sticky_exit_callback(
crate::event::Event::Resumed,
&self.target,
&mut control_flow,
callback,
);
callback(crate::event::Event::Resumed, &self.target);
}
// Process all pending events
self.drain_events(callback, &mut control_flow);
self.drain_events(callback);
// Empty activation tokens.
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
@@ -629,7 +567,7 @@ impl<T: 'static> EventLoop<T> {
});
match token {
Some(Ok(token)) => sticky_exit_callback(
Some(Ok(token)) => callback(
crate::event::Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: crate::event::WindowEvent::ActivationTokenDone {
@@ -638,8 +576,6 @@ impl<T: 'static> EventLoop<T> {
},
},
&self.target,
&mut control_flow,
callback,
),
Some(Err(e)) => {
log::error!("Failed to get activation token: {}", e);
@@ -651,12 +587,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_receiver.try_recv() {
sticky_exit_callback(
crate::event::Event::UserEvent(event),
&self.target,
&mut control_flow,
callback,
);
callback(crate::event::Event::UserEvent(event), &self.target);
}
}
@@ -670,31 +601,25 @@ impl<T: 'static> EventLoop<T> {
for window_id in windows {
let window_id = crate::window::WindowId(window_id);
sticky_exit_callback(
Event::RedrawRequested(window_id),
callback(
Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
},
&self.target,
&mut control_flow,
callback,
);
}
}
// This is always the last event we dispatch before poll again
{
sticky_exit_callback(
crate::event::Event::AboutToWait,
&self.target,
&mut control_flow,
callback,
);
callback(crate::event::Event::AboutToWait, &self.target);
}
self.control_flow = control_flow;
}
fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow)
fn drain_events<F>(&mut self, callback: &mut F)
where
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>),
{
let target = &self.target;
let mut xev = MaybeUninit::uninit();
@@ -703,21 +628,34 @@ impl<T: 'static> EventLoop<T> {
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, |event| {
sticky_exit_callback(
event,
target,
control_flow,
&mut |event, window_target, control_flow| {
if let Event::RedrawRequested(crate::window::WindowId(wid)) = event {
wt.redraw_sender.send(wid).unwrap();
} else {
callback(event, window_target, control_flow);
}
},
);
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
wt.redraw_sender.send(wid).unwrap();
} else {
callback(event, target);
}
});
}
}
fn control_flow(&self) -> ControlFlow {
self.target.p.control_flow()
}
fn exiting(&self) -> bool {
self.target.p.exiting()
}
fn set_exit_code(&self, code: i32) {
self.target.p.set_exit_code(code)
}
fn exit_code(&self) -> Option<i32> {
self.target.p.exit_code()
}
}
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
@@ -735,7 +673,15 @@ impl<T> EventLoopWindowTarget<T> {
&self.xconn
}
pub fn set_listen_device_events(&self, allowed: DeviceEvents) {
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.xconn.available_monitors().into_iter().flatten()
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.xconn.primary_monitor().ok()
}
pub fn listen_device_events(&self, allowed: DeviceEvents) {
self.device_events.set(allowed);
}
@@ -754,15 +700,55 @@ impl<T> EventLoopWindowTarget<T> {
}
self.xconn
.select_xinput_events(self.root, ffi::XIAllMasterDevices as _, mask)
.select_xinput_events(self.root, ALL_MASTER_DEVICES, mask)
.expect_then_ignore_error("Failed to update device event filter");
}
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
let mut display_handle = XlibDisplayHandle::empty();
#[cfg(feature = "rwh_05")]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
let mut display_handle = rwh_05::XlibDisplayHandle::empty();
display_handle.display = self.xconn.display as *mut _;
display_handle.screen = self.xconn.default_screen_index() as c_int;
RawDisplayHandle::Xlib(display_handle)
display_handle.into()
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
let display_handle = rwh_06::XlibDisplayHandle::new(
// SAFETY: display will never be null
Some(
std::ptr::NonNull::new(self.xconn.display as *mut _)
.expect("X11 display should never be null"),
),
self.xconn.default_screen_index() as c_int,
);
Ok(display_handle.into())
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
}
@@ -815,7 +801,7 @@ impl<'a> Deref for DeviceInfo<'a> {
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
pub struct DeviceId(xinput::DeviceId);
impl DeviceId {
#[allow(unused)]
@@ -887,6 +873,9 @@ pub enum X11Error {
/// Got an invalid activation token.
InvalidActivationToken(Vec<u8>),
/// An extension that we rely on is not available.
MissingExtension(&'static str),
/// Could not find a matching X11 visual for this visualid
NoSuchVisual(xproto::Visualid),
}
@@ -905,6 +894,7 @@ impl fmt::Display for X11Error {
"Invalid activation token: {}",
std::str::from_utf8(s).unwrap_or("<invalid utf8>")
),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s),
X11Error::NoSuchVisual(visualid) => {
write!(
f,
@@ -1026,17 +1016,10 @@ impl<'a> Drop for GenericEventCookie<'a> {
}
}
#[derive(Debug, Default, Copy, Clone)]
struct XExtension {
opcode: c_int,
first_event_id: c_int,
first_error_id: c_int,
}
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
}
fn mkdid(w: c_int) -> crate::event::DeviceId {
fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId {
crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w)))
}
@@ -1069,12 +1052,10 @@ impl Device {
if Device::physical_device(info) {
// Identify scroll axes
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
if class._type == ffi::XIScrollClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
};
for &class_ptr in Device::classes(info) {
let ty = unsafe { (*class_ptr)._type };
if ty == ffi::XIScrollClass {
let info = unsafe { &*(class_ptr as *const ffi::XIScrollClassInfo) };
scroll_axes.push((
info.number,
ScrollAxis {
@@ -1102,12 +1083,10 @@ impl Device {
fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) {
if Device::physical_device(info) {
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
if class._type == ffi::XIValuatorClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
};
for &class_ptr in Device::classes(info) {
let ty = unsafe { (*class_ptr)._type };
if ty == ffi::XIValuatorClass {
let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) };
if let Some(&mut (_, ref mut axis)) = self
.scroll_axes
.iter_mut()

View File

@@ -1,29 +1,24 @@
use std::os::raw::*;
use std::slice;
use std::sync::Mutex;
use once_cell::sync::Lazy;
use super::{
ffi::{
self, RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources,
},
util, X11Error, XConnection,
};
use super::{util, X11Error, XConnection};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
platform_impl::VideoMode as PlatformVideoMode,
};
use x11rb::{
connection::RequestConnection,
protocol::{
randr::{self, ConnectionExt as _},
xproto,
},
};
// Used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false;
static MONITORS: Lazy<Mutex<Option<Vec<MonitorHandle>>>> = Lazy::new(Mutex::default);
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
// We update this lazily.
(*MONITORS.lock().unwrap()).take()
impl XConnection {
pub fn invalidate_cached_monitor_list(&self) -> Option<Vec<MonitorHandle>> {
// We update this lazily.
self.monitor_handles.lock().unwrap().take()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -31,7 +26,7 @@ pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) native_mode: RRMode,
pub(crate) native_mode: randr::Mode,
pub(crate) monitor: Option<MonitorHandle>,
}
@@ -52,15 +47,15 @@ impl VideoMode {
}
#[inline]
pub fn monitor(&self) -> PlatformMonitorHandle {
PlatformMonitorHandle::X(self.monitor.clone().unwrap())
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone().unwrap()
}
}
#[derive(Debug, Clone)]
pub struct MonitorHandle {
/// The actual id
pub(crate) id: RRCrtc,
pub(crate) id: randr::Crtc,
/// The name of the monitor
pub(crate) name: String,
/// The size of the monitor
@@ -106,10 +101,10 @@ impl std::hash::Hash for MonitorHandle {
}
#[inline]
pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> {
if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 {
pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option<u32> {
if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 {
#[allow(clippy::unnecessary_cast)]
Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32)
Some((mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32)
} else {
None
}
@@ -118,19 +113,18 @@ pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> {
impl MonitorHandle {
fn new(
xconn: &XConnection,
resources: *mut XRRScreenResources,
id: RRCrtc,
crtc: *mut XRRCrtcInfo,
resources: &ScreenResources,
id: randr::Crtc,
crtc: &randr::GetCrtcInfoReply,
primary: bool,
) -> Option<Self> {
let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let dimensions = unsafe { ((*crtc).width, (*crtc).height) };
let position = unsafe { ((*crtc).x, (*crtc).y) };
let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?;
let dimensions = (crtc.width as u32, crtc.height as u32);
let position = (crtc.x as i32, crtc.y as i32);
// Get the refresh rate of the current video mode.
let current_mode = unsafe { (*crtc).mode };
let screen_modes =
unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) };
let current_mode = crtc.mode;
let screen_modes = resources.modes();
let refresh_rate_millihertz = screen_modes
.iter()
.find(|mode| mode.id == current_mode)
@@ -207,19 +201,22 @@ impl MonitorHandle {
}
impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
let monitors = self.available_monitors();
pub fn get_monitor_for_window(
&self,
window_rect: Option<util::AaRect>,
) -> Result<MonitorHandle, X11Error> {
let monitors = self.available_monitors()?;
if monitors.is_empty() {
// Return a dummy monitor to avoid panicking
return MonitorHandle::dummy();
return Ok(MonitorHandle::dummy());
}
let default = monitors.get(0).unwrap();
let window_rect = match window_rect {
Some(rect) => rect,
None => return default.to_owned(),
None => return Ok(default.to_owned()),
};
let mut largest_overlap = 0;
@@ -232,110 +229,153 @@ impl XConnection {
}
}
matched_monitor.to_owned()
Ok(matched_monitor.to_owned())
}
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, X11Error> {
let root = self.default_root();
let resources = ScreenResources::from_connection(self.xcb_connection(), root)?;
let root = self.default_root().root;
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window)
} else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
// Upon failure, `resources` will be null.
(self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window)
};
if resources.is_null() {
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
}
let mut has_primary = false;
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root as ffi::Window);
let mut available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let is_primary = *(*crtc).outputs.offset(0) == primary;
has_primary |= is_primary;
if let Some(monitor_id) =
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
{
available.push(monitor_id)
}
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
}
// If no monitors were detected as being primary, we just pick one ourselves!
if !has_primary {
if let Some(ref mut fallback) = available.first_mut() {
// Setting this here will come in handy if we ever add an `is_primary` method.
fallback.primary = true;
}
}
(self.xrandr.XRRFreeScreenResources)(resources);
available
// Pipeline all of the get-crtc requests.
let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
for &crtc in resources.crtcs() {
crtc_cookies.push(
self.xcb_connection()
.randr_get_crtc_info(crtc, x11rb::CURRENT_TIME)?,
);
}
// Do this here so we do all of our requests in one shot.
let primary = self
.xcb_connection()
.randr_get_output_primary(root.root)?
.reply()?
.output;
let mut crtc_infos = Vec::with_capacity(crtc_cookies.len());
for cookie in crtc_cookies {
let reply = cookie.reply()?;
crtc_infos.push(reply);
}
let mut has_primary = false;
let mut available_monitors = Vec::with_capacity(resources.crtcs().len());
for (crtc_id, crtc) in resources.crtcs().iter().zip(crtc_infos.iter()) {
if crtc.width == 0 || crtc.height == 0 || crtc.outputs.is_empty() {
continue;
}
let is_primary = crtc.outputs[0] == primary;
has_primary |= is_primary;
let monitor = MonitorHandle::new(self, &resources, *crtc_id, crtc, is_primary);
available_monitors.extend(monitor);
}
// If we don't have a primary monitor, just pick one ourselves!
if !has_primary {
if let Some(ref mut fallback) = available_monitors.first_mut() {
// Setting this here will come in handy if we ever add an `is_primary` method.
fallback.primary = true;
}
}
Ok(available_monitors)
}
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
let mut monitors_lock = MONITORS.lock().unwrap();
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
let mut monitors_lock = self.monitor_handles.lock().unwrap();
(*monitors_lock)
.as_ref()
.cloned()
.map(Ok)
.or_else(|| {
let monitors = Some(self.query_monitor_list());
if !DISABLE_MONITOR_LIST_CACHING {
(*monitors_lock) = monitors.clone();
}
monitors
self.query_monitor_list()
.map(|mon_list| {
let monitors = Some(mon_list);
if !DISABLE_MONITOR_LIST_CACHING {
(*monitors_lock) = monitors.clone();
}
monitors
})
.transpose()
})
.unwrap()
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
self.available_monitors()
pub fn primary_monitor(&self) -> Result<MonitorHandle, X11Error> {
Ok(self
.available_monitors()?
.into_iter()
.find(|monitor| monitor.primary)
.unwrap_or_else(MonitorHandle::dummy)
.unwrap_or_else(MonitorHandle::dummy))
}
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, X11Error> {
let has_xrandr = unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
};
assert!(
has_xrandr == True,
"[winit] XRandR extension not available."
);
pub fn select_xrandr_input(&self, root: xproto::Window) -> Result<u8, X11Error> {
use randr::NotifyMask;
let mut event_offset = 0;
let mut error_offset = 0;
let status = unsafe {
(self.xrandr.XRRQueryExtension)(self.display, &mut event_offset, &mut error_offset)
};
// Get extension info.
let info = self
.xcb_connection()
.extension_information(randr::X11_EXTENSION_NAME)?
.ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
if status != True {
self.check_errors()?;
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
}
// Select input data.
let event_mask =
NotifyMask::CRTC_CHANGE | NotifyMask::OUTPUT_PROPERTY | NotifyMask::SCREEN_CHANGE;
self.xcb_connection().randr_select_input(root, event_mask)?;
let mask = RRCrtcChangeNotifyMask | RROutputPropertyNotifyMask | RRScreenChangeNotifyMask;
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
Ok(event_offset)
Ok(info.first_event)
}
}
pub(crate) struct ScreenResources {
/// List of attached modes.
modes: Vec<randr::ModeInfo>,
/// List of attached CRTCs.
crtcs: Vec<randr::Crtc>,
}
impl ScreenResources {
pub(crate) fn modes(&self) -> &[randr::ModeInfo] {
&self.modes
}
pub(crate) fn crtcs(&self) -> &[randr::Crtc] {
&self.crtcs
}
pub(crate) fn from_connection(
conn: &impl x11rb::connection::Connection,
root: &x11rb::protocol::xproto::Screen,
) -> Result<Self, X11Error> {
let version = conn.randr_query_version(0, 0)?.reply()?;
if (version.major_version == 1 && version.minor_version >= 3) || version.major_version > 1 {
let reply = conn
.randr_get_screen_resources_current(root.root)?
.reply()?;
Ok(Self::from_get_screen_resources_current_reply(reply))
} else {
let reply = conn.randr_get_screen_resources(root.root)?.reply()?;
Ok(Self::from_get_screen_resources_reply(reply))
}
}
pub(crate) fn from_get_screen_resources_reply(reply: randr::GetScreenResourcesReply) -> Self {
Self {
modes: reply.modes,
crtcs: reply.crtcs,
}
}
pub(crate) fn from_get_screen_resources_current_reply(
reply: randr::GetScreenResourcesCurrentReply,
) -> Self {
Self {
modes: reply.modes,
crtcs: reply.crtcs,
}
}
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::assertions_on_constants)]
use super::*;
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
use crate::icon::{Pixel, RgbaIcon, PIXEL_SIZE};
impl Pixel {
pub fn to_packed_argb(&self) -> Cardinal {
@@ -18,16 +18,15 @@ impl Pixel {
}
}
impl Icon {
impl RgbaIcon {
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
let rgba_icon = &self.inner;
assert_eq!(rgba_icon.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = rgba_icon.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (rgba_icon.width * rgba_icon.height) as usize);
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = self.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (self.width * self.height) as usize);
let mut data = Vec::with_capacity(pixel_count);
data.push(rgba_icon.width as Cardinal);
data.push(rgba_icon.height as Cardinal);
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
data.push(self.width as Cardinal);
data.push(self.height as Cardinal);
let pixels = self.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.add(pixel_index) };
data.push(pixel.to_packed_argb());

View File

@@ -1,5 +1,8 @@
use std::{slice, str};
use x11rb::protocol::xinput::{self, ConnectionExt as _};
use x11rb::protocol::{
xinput::{self, ConnectionExt as _},
xkb,
};
use super::*;
@@ -11,31 +14,6 @@ pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
// To test if `lookup_utf8` works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
// NOTE: Some of these fields are not used, but may be of use in the future.
pub struct PointerState<'a> {
xconn: &'a XConnection,
pub root: xproto::Window,
pub child: xproto::Window,
pub root_x: c_double,
pub root_y: c_double,
pub win_x: c_double,
pub win_y: c_double,
buttons: ffi::XIButtonState,
pub group: ffi::XIGroupState,
pub relative_to_window: bool,
}
impl<'a> Drop for PointerState<'a> {
fn drop(&mut self) {
if !self.buttons.mask.is_null() {
unsafe {
// This is why you need to read the docs carefully...
(self.xconn.xlib.XFree)(self.buttons.mask as _);
}
}
}
}
impl XConnection {
pub fn select_xinput_events(
&self,
@@ -54,7 +32,12 @@ impl XConnection {
.map_err(Into::into)
}
pub fn select_xkb_events(&self, device_id: c_int, mask: c_ulong) -> Result<bool, X11Error> {
pub fn select_xkb_events(
&self,
device_id: xkb::DeviceSpec,
mask: xkb::EventType,
) -> Result<bool, X11Error> {
let mask = u16::from(mask) as _;
let status =
unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) };

View File

@@ -1,12 +1,11 @@
use std::{env, slice, str::FromStr};
use std::{env, str, str::FromStr};
use super::{
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use super::*;
use crate::platform_impl::platform::x11::monitor;
use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode};
use x11rb::protocol::randr::{self, ConnectionExt as _};
/// Represents values of `WINIT_HIDPI_FACTOR`.
pub enum EnvVarDPI {
Randr,
@@ -37,42 +36,32 @@ pub fn calc_dpi_factor(
impl XConnection {
// Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
(self.xlib.XrmInitialize)();
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
if resource_manager_str.is_null() {
return None;
}
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
let name: &str = "Xft.dpi:\t";
for pair in res.split('\n') {
if let Some(stripped) = pair.strip_prefix(name) {
return f64::from_str(stripped).ok();
}
}
}
None
pub fn get_xft_dpi(&self) -> Option<f64> {
self.database()
.get_string("Xfi.dpi", "")
.and_then(|s| f64::from_str(s).ok())
}
pub unsafe fn get_output_info(
pub fn get_output_info(
&self,
resources: *mut XRRScreenResources,
crtc: *mut XRRCrtcInfo,
resources: &monitor::ScreenResources,
crtc: &randr::GetCrtcInfoReply,
) -> Option<(String, f64, Vec<VideoMode>)> {
let output_info =
(self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0));
if output_info.is_null() {
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
// it's possible for it to return null.
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596
let _ = self.check_errors(); // discard `BadRROutput` error
return None;
}
let output_info = match self
.xcb_connection()
.randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME)
.map_err(X11Error::from)
.and_then(|r| r.reply().map_err(X11Error::from))
{
Ok(output_info) => output_info,
Err(err) => {
warn!("Failed to get output info: {:?}", err);
return None;
}
};
let bit_depth = self.default_root().root_depth;
let output_modes =
slice::from_raw_parts((*output_info).modes, (*output_info).nmode as usize);
let resource_modes = slice::from_raw_parts((*resources).modes, (*resources).nmode as usize);
let output_modes = &output_info.modes;
let resource_modes = resources.modes();
let modes = resource_modes
.iter()
@@ -81,7 +70,7 @@ impl XConnection {
.filter(|x| output_modes.iter().any(|id| x.id == *id))
.map(|mode| {
VideoMode {
size: (mode.width, mode.height),
size: (mode.width.into(), mode.height.into()),
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode)
.unwrap_or(0),
bit_depth: bit_depth as u16,
@@ -93,11 +82,13 @@ impl XConnection {
})
.collect();
let name_slice = slice::from_raw_parts(
(*output_info).name as *mut u8,
(*output_info).nameLen as usize,
);
let name = String::from_utf8_lossy(name_slice).into();
let name = match str::from_utf8(&output_info.name) {
Ok(name) => name.to_owned(),
Err(err) => {
warn!("Failed to get output name: {:?}", err);
return None;
}
};
// Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set
let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok();
if deprecated_dpi_override.is_some() {
@@ -124,8 +115,8 @@ impl XConnection {
let scale_factor = match dpi_env {
EnvVarDPI::Randr => calc_dpi_factor(
((*crtc).width, (*crtc).height),
((*output_info).mm_width as _, (*output_info).mm_height as _),
(crtc.width.into(), crtc.height.into()),
(output_info.mm_width as _, output_info.mm_height as _),
),
EnvVarDPI::Scale(dpi_override) => {
if !validate_scale_factor(dpi_override) {
@@ -140,74 +131,47 @@ impl XConnection {
dpi / 96.
} else {
calc_dpi_factor(
((*crtc).width, (*crtc).height),
((*output_info).mm_width as _, (*output_info).mm_height as _),
(crtc.width.into(), crtc.height.into()),
(output_info.mm_width as _, output_info.mm_height as _),
)
}
}
};
(self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, scale_factor, modes))
}
#[must_use]
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
pub fn set_crtc_config(
&self,
crtc_id: randr::Crtc,
mode_id: randr::Mode,
) -> Result<(), X11Error> {
let crtc = self
.xcb_connection()
.randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?
.reply()?;
let root = self.default_root().root;
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let status = (self.xrandr.XRRSetCrtcConfig)(
self.display,
resources,
self.xcb_connection()
.randr_set_crtc_config(
crtc_id,
CurrentTime,
(*crtc).x,
(*crtc).y,
crtc.timestamp,
x11rb::CURRENT_TIME,
crtc.x,
crtc.y,
mode_id,
(*crtc).rotation,
(*crtc).outputs.offset(0),
1,
);
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
if status == Success as i32 {
Some(())
} else {
None
}
}
crtc.rotation,
&crtc.outputs,
)?
.reply()
.map(|_| ())
.map_err(Into::into)
}
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = self.default_root().root;
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let mode = (*crtc).mode;
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
mode
}
pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result<randr::Mode, X11Error> {
Ok(self
.xcb_connection()
.randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?
.reply()?
.mode)
}
}

View File

@@ -7,15 +7,16 @@ use std::{
sync::{Arc, Mutex, MutexGuard},
};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle};
use x11rb::{
connection::Connection,
properties::WmHintsState,
protocol::xproto::{self, ConnectionExt as _},
};
use x11rb::{
properties::{WmHints, WmSizeHints, WmSizeHintsSpecification},
protocol::xinput,
properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
protocol::{
randr,
shape::SK,
xfixes::{ConnectionExt, RegionWrapper},
xinput,
xproto::{self, ConnectionExt as _, Rectangle},
},
};
use crate::{
@@ -24,11 +25,11 @@ use crate::{
event_loop::AsyncRequestSerial,
platform_impl::{
x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error},
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError,
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
},
window::{
CursorGrabMode, CursorIcon, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
},
};
@@ -55,7 +56,7 @@ pub struct SharedState {
// Used to restore position after exiting fullscreen
pub restore_position: Option<(i32, i32)>,
// Used to restore video mode after exiting fullscreen
pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>,
pub desktop_video_mode: Option<(randr::Crtc, randr::Mode)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<Size>,
pub max_inner_size: Option<Size>,
@@ -63,6 +64,7 @@ pub struct SharedState {
pub base_size: Option<Size>,
pub visibility: Visibility,
pub has_focus: bool,
pub cursor_hittest: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -103,6 +105,7 @@ impl SharedState {
resize_increments: None,
base_size: None,
has_focus: false,
cursor_hittest: true,
})
}
}
@@ -113,9 +116,11 @@ unsafe impl Sync for UnownedWindow {}
pub(crate) struct UnownedWindow {
pub(crate) xconn: Arc<XConnection>, // never changes
xwindow: xproto::Window, // never changes
visual: u32, // never changes
#[allow(dead_code)]
visual: u32, // never changes
root: xproto::Window, // never changes
screen_id: i32, // never changes
#[allow(dead_code)]
screen_id: i32, // never changes
cursor: Mutex<CursorIcon>,
cursor_grabbed_mode: Mutex<CursorGrabMode>,
#[allow(clippy::mutex_atomic)]
@@ -144,14 +149,17 @@ impl UnownedWindow {
) -> Result<UnownedWindow, RootOsError> {
let xconn = &event_loop.xconn;
let atoms = xconn.atoms();
#[cfg(feature = "rwh_06")]
let root = match window_attrs.parent_window {
Some(RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
Some(RawWindowHandle::Xcb(handle)) => handle.window,
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,
};
#[cfg(not(feature = "rwh_06"))]
let root = event_loop.root;
let mut monitors = xconn.available_monitors();
let mut monitors = leap!(xconn.available_monitors());
let guessed_monitor = if monitors.is_empty() {
X11MonitorHandle::dummy()
} else {
@@ -243,7 +251,7 @@ impl UnownedWindow {
// Find a suitable visual, true color with 32 bits of depth.
all_visuals
.find_map(|(visual, depth)| {
(visual.class == xproto::VisualClass::TRUE_COLOR)
(depth == 32 && visual.class == xproto::VisualClass::TRUE_COLOR)
.then_some((Some(visual), depth, true))
})
.unwrap_or_else(|| {
@@ -470,7 +478,7 @@ impl UnownedWindow {
// Set window icons
if let Some(icon) = window_attrs.window_icon {
leap!(window.set_icon_inner(icon)).ignore_error();
leap!(window.set_icon_inner(icon.inner)).ignore_error();
}
// Opt into handling window close
@@ -524,7 +532,7 @@ impl UnownedWindow {
| xinput::XIEventMask::TOUCH_BEGIN
| xinput::XIEventMask::TOUCH_UPDATE
| xinput::XIEventMask::TOUCH_END;
leap!(xconn.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices as u16, mask))
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
{
@@ -740,8 +748,12 @@ impl UnownedWindow {
&Some(Fullscreen::Exclusive(PlatformVideoMode::X(ref video_mode))),
) => {
let monitor = video_mode.monitor.as_ref().unwrap();
shared_state_lock.desktop_video_mode =
Some((monitor.id, self.xconn.get_crtc_mode(monitor.id)));
shared_state_lock.desktop_video_mode = Some((
monitor.id,
self.xconn
.get_crtc_mode(monitor.id)
.expect("Failed to get desktop video mode"),
));
}
// Restore desktop video mode upon exiting exclusive fullscreen
(&Some(Fullscreen::Exclusive(_)), &None)
@@ -775,7 +787,9 @@ impl UnownedWindow {
Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => {
(None, monitor)
}
Fullscreen::Borderless(None) => (None, self.current_monitor()),
Fullscreen::Borderless(None) => {
(None, self.shared_state_lock().last_monitor.clone())
}
#[cfg(wayland_platform)]
_ => unreachable!(),
};
@@ -871,17 +885,22 @@ impl UnownedWindow {
}
}
#[inline]
pub fn current_monitor(&self) -> X11MonitorHandle {
self.shared_state_lock().last_monitor.clone()
pub fn current_monitor(&self) -> Option<X11MonitorHandle> {
Some(self.shared_state_lock().last_monitor.clone())
}
pub fn available_monitors(&self) -> Vec<X11MonitorHandle> {
self.xconn.available_monitors()
self.xconn
.available_monitors()
.expect("Failed to get available monitors")
}
pub fn primary_monitor(&self) -> X11MonitorHandle {
self.xconn.primary_monitor()
pub fn primary_monitor(&self) -> Option<X11MonitorHandle> {
Some(
self.xconn
.primary_monitor()
.expect("Failed to get primary monitor"),
)
}
#[inline]
@@ -1017,6 +1036,9 @@ impl UnownedWindow {
#[inline]
pub fn set_transparent(&self, _transparent: bool) {}
#[inline]
pub fn set_blur(&self, _blur: bool) {}
fn set_decorations_inner(&self, decorations: bool) -> Result<VoidCookie<'_>, X11Error> {
self.shared_state_lock().is_decorated = decorations;
let mut hints = self.xconn.get_motif_hints(self.xwindow);
@@ -1070,7 +1092,7 @@ impl UnownedWindow {
.expect("Failed to set window-level state");
}
fn set_icon_inner(&self, icon: Icon) -> Result<VoidCookie<'_>, X11Error> {
fn set_icon_inner(&self, icon: PlatformIcon) -> Result<VoidCookie<'_>, X11Error> {
let atoms = self.xconn.atoms();
let icon_atom = atoms[_NET_WM_ICON];
let data = icon.to_cardinals();
@@ -1097,7 +1119,7 @@ impl UnownedWindow {
}
#[inline]
pub fn set_window_icon(&self, icon: Option<Icon>) {
pub(crate) fn set_window_icon(&self, icon: Option<PlatformIcon>) {
match icon {
Some(icon) => self.set_icon_inner(icon),
None => self.unset_icon_inner(),
@@ -1273,6 +1295,10 @@ impl UnownedWindow {
self.xconn
.flush_requests()
.expect("Failed to call XResizeWindow");
// cursor_hittest needs to be reapplied after window resize
if self.shared_state_lock().cursor_hittest {
let _ = self.set_cursor_hittest(true);
}
}
#[inline]
@@ -1442,11 +1468,13 @@ impl UnownedWindow {
WindowButtons::all()
}
#[allow(dead_code)]
#[inline]
pub fn xlib_display(&self) -> *mut c_void {
self.xconn.display as _
}
#[allow(dead_code)]
#[inline]
pub fn xlib_window(&self) -> c_ulong {
self.xwindow as ffi::Window
@@ -1558,7 +1586,7 @@ impl UnownedWindow {
#[inline]
pub fn scale_factor(&self) -> f64 {
self.current_monitor().scale_factor
self.shared_state_lock().last_monitor.scale_factor
}
pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> {
@@ -1582,8 +1610,25 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
let mut rectangles: Vec<Rectangle> = Vec::new();
if hittest {
let size = self.inner_size();
rectangles.push(Rectangle {
x: 0,
y: 0,
width: size.width as u16,
height: size.height as u16,
})
}
let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles)
.map_err(|_e| ExternalError::Ignored)?;
self.xconn
.xcb_connection()
.xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region())
.map_err(|_e| ExternalError::Ignored)?;
self.shared_state_lock().cursor_hittest = hittest;
Ok(())
}
/// Moves the window while it is being dragged.
@@ -1591,6 +1636,9 @@ impl UnownedWindow {
self.drag_initiate(util::MOVERESIZE_MOVE)
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
/// Resizes the window while it is being dragged.
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.drag_initiate(match direction {
@@ -1778,20 +1826,55 @@ impl UnownedWindow {
// TODO timer
}
#[cfg(feature = "rwh_04")]
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = XlibWindowHandle::empty();
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::XlibHandle::empty();
window_handle.display = self.xlib_display();
window_handle.window = self.xlib_window();
window_handle.visual_id = self.visual as c_ulong;
RawWindowHandle::Xlib(window_handle)
rwh_04::RawWindowHandle::Xlib(window_handle)
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = XlibDisplayHandle::empty();
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::XlibWindowHandle::empty();
window_handle.window = self.xlib_window();
window_handle.visual_id = self.visual as c_ulong;
window_handle.into()
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
let mut display_handle = rwh_05::XlibDisplayHandle::empty();
display_handle.display = self.xlib_display();
display_handle.screen = self.screen_id;
RawDisplayHandle::Xlib(display_handle)
display_handle.into()
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let mut window_handle = rwh_06::XlibWindowHandle::new(self.xlib_window());
window_handle.visual_id = self.visual as c_ulong;
Ok(window_handle.into())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::XlibDisplayHandle::new(
Some(
std::ptr::NonNull::new(self.xlib_display())
.expect("display pointer should never be null"),
),
self.screen_id,
)
.into())
}
#[inline]
@@ -1799,6 +1882,8 @@ impl UnownedWindow {
None
}
pub fn set_content_protected(&self, _protected: bool) {}
#[inline]
pub fn has_focus(&self) -> bool {
self.shared_state_lock().has_focus

View File

@@ -10,17 +10,20 @@ use std::{
use crate::window::CursorIcon;
use super::{atoms::Atoms, ffi};
use x11rb::{connection::Connection, protocol::xproto, xcb_ffi::XCBConnection};
use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
/// A connection to an X server.
pub(crate) struct XConnection {
pub xlib: ffi::Xlib,
/// Exposes XRandR functions from version < 1.5
pub xrandr: ffi::Xrandr_2_2_0,
pub xcursor: ffi::Xcursor,
// TODO(notgull): I'd like to remove this, but apparently Xlib and Xinput2 are tied together
// for some reason.
pub xinput2: ffi::XInput2,
pub display: *mut ffi::Display,
/// The manager for the XCB connection.
///
/// The `Option` ensures that we can drop it before we close the `Display`.
@@ -38,6 +41,12 @@ pub(crate) struct XConnection {
/// The last timestamp received by this connection.
timestamp: AtomicU32,
/// List of monitor handles.
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
/// The resource database.
database: resource_manager::Database,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
}
@@ -53,9 +62,8 @@ impl XConnection {
// opening the libraries
let xlib = ffi::Xlib::open()?;
let xcursor = ffi::Xcursor::open()?;
let xrandr = ffi::Xrandr_2_2_0::open()?;
let xinput2 = ffi::XInput2::open()?;
let xlib_xcb = ffi::Xlib_xcb::open()?;
let xinput2 = ffi::XInput2::open()?;
unsafe { (xlib.XInitThreads)() };
unsafe { (xlib.XSetErrorHandler)(error_handler) };
@@ -92,9 +100,12 @@ impl XConnection {
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Load the database.
let database = resource_manager::new_from_default(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
Ok(XConnection {
xlib,
xrandr,
xcursor,
xinput2,
display,
@@ -103,6 +114,8 @@ impl XConnection {
default_screen,
timestamp: AtomicU32::new(0),
latest_error: Mutex::new(None),
monitor_handles: Mutex::new(None),
database,
cursor_cache: Default::default(),
})
}
@@ -144,6 +157,12 @@ impl XConnection {
&self.xcb_connection().setup().roots[self.default_screen]
}
/// Get the resource database.
#[inline]
pub fn database(&self) -> &resource_manager::Database {
&self.database
}
/// Get the latest timestamp.
#[inline]
pub fn timestamp(&self) -> u32 {

View File

@@ -4,7 +4,7 @@ use icrate::Foundation::NSObject;
use objc2::{declare_class, msg_send, mutability, ClassType};
use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use super::{app_state::AppState, event::EventWrapper, DEVICE_ID};
use super::{app_state::AppState, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event};
declare_class!(
@@ -96,5 +96,5 @@ fn queue_device_event(event: DeviceEvent) {
device_id: DEVICE_ID,
event,
};
AppState::queue_event(EventWrapper::StaticEvent(event));
AppState::queue_event(event);
}

View File

@@ -56,7 +56,7 @@ declare_class!(
fn will_terminate(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
AppState::exit();
AppState::internal_exit();
}
}
);

View File

@@ -6,29 +6,24 @@ use std::{
rc::{Rc, Weak},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, MutexGuard,
mpsc, Arc, Mutex, MutexGuard,
},
time::Instant,
};
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
use icrate::Foundation::{is_main_thread, NSSize};
use objc2::rc::autoreleasepool;
use objc2::rc::{autoreleasepool, Id};
use once_cell::sync::Lazy;
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent};
use super::{
event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never, window::WinitWindow,
};
use crate::{
dpi::LogicalSize,
dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform_impl::platform::{
event::{EventProxy, EventWrapper},
event_loop::PanicInfo,
menu,
observer::EventLoopWaker,
util::Never,
window::WinitWindow,
},
window::WindowId,
};
@@ -45,24 +40,22 @@ impl<Never> Event<Never> {
pub trait EventHandler: Debug {
// Not sure probably it should accept Event<'static, Never>
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
fn handle_nonuser_event(&mut self, event: Event<Never>);
fn handle_user_events(&mut self);
}
pub(crate) type Callback<T> = RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>;
pub(crate) type Callback<T> = RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>)>;
struct EventLoopHandler<T: 'static> {
callback: Weak<Callback<T>>,
window_target: Rc<RootWindowTarget<T>>,
receiver: Rc<mpsc::Receiver<T>>,
}
impl<T> EventLoopHandler<T> {
fn with_callback<F>(&mut self, f: F)
where
F: FnOnce(
&mut EventLoopHandler<T>,
RefMut<'_, dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>,
),
F: FnOnce(&mut EventLoopHandler<T>, RefMut<'_, dyn FnMut(Event<T>, &RootWindowTarget<T>)>),
{
// The `NSApp` and our `HANDLER` are global state and so it's possible that
// we could get a delegate callback after the application has exit an
@@ -89,33 +82,31 @@ impl<T> Debug for EventLoopHandler<T> {
}
impl<T> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
fn handle_nonuser_event(&mut self, event: Event<Never>) {
self.with_callback(|this, mut callback| {
if let ControlFlow::ExitWithCode(code) = *control_flow {
// XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode?
let dummy = &mut ControlFlow::ExitWithCode(code);
(callback)(event.userify(), &this.window_target, dummy);
} else {
(callback)(event.userify(), &this.window_target, control_flow);
}
(callback)(event.userify(), &this.window_target);
});
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
fn handle_user_events(&mut self) {
self.with_callback(|this, mut callback| {
for event in this.window_target.p.receiver.try_iter() {
if let ControlFlow::ExitWithCode(code) = *control_flow {
// XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode?
let dummy = &mut ControlFlow::ExitWithCode(code);
(callback)(Event::UserEvent(event), &this.window_target, dummy);
} else {
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
}
for event in this.receiver.try_iter() {
(callback)(Event::UserEvent(event), &this.window_target);
}
});
}
}
#[derive(Debug)]
enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged {
window: Id<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
},
}
#[derive(Default)]
struct Handler {
stop_app_on_launch: AtomicBool,
@@ -126,6 +117,7 @@ struct Handler {
running: AtomicBool,
in_callback: AtomicBool,
control_flow: Mutex<ControlFlow>,
exit: AtomicBool,
start_time: Mutex<Option<Instant>>,
callback: Mutex<Option<Box<dyn EventHandler>>>,
pending_events: Mutex<VecDeque<EventWrapper>>,
@@ -183,13 +175,6 @@ impl Handler {
self.running.store(true, Ordering::Relaxed);
}
fn should_exit(&self) -> bool {
matches!(
*self.control_flow.lock().unwrap(),
ControlFlow::ExitWithCode(_)
)
}
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits
///
/// Since an `EventLoop` may be run more than once we need make sure to reset the
@@ -200,7 +185,7 @@ impl Handler {
///
/// # Caveat
/// This is only intended to be called from the main thread
fn exit(&self) {
fn internal_exit(&self) {
// Relaxed ordering because we don't actually have multiple threads involved, we just want
// interiour mutability
//
@@ -214,13 +199,20 @@ impl Handler {
// looks like there have been recuring re-entrancy issues with callback handling that might
// make that awkward)
self.running.store(false, Ordering::Relaxed);
*self.control_flow.lock().unwrap() = ControlFlow::default();
self.set_stop_app_on_redraw_requested(false);
self.set_stop_app_before_wait(false);
self.set_stop_app_after_wait(false);
self.set_wait_timeout(None);
}
pub fn exit(&self) {
self.exit.store(true, Ordering::Relaxed)
}
pub fn exiting(&self) -> bool {
self.exit.load(Ordering::Relaxed)
}
pub fn request_stop_app_on_launch(&self) {
// Relaxed ordering because we don't actually have multiple threads involved, we just want
// interior mutability
@@ -281,6 +273,10 @@ impl Handler {
self.stop_app_on_redraw.load(Ordering::Relaxed)
}
fn set_control_flow(&self, new_control_flow: ControlFlow) {
*self.control_flow.lock().unwrap() = new_control_flow
}
fn control_flow(&self) -> ControlFlow {
*self.control_flow.lock().unwrap()
}
@@ -313,60 +309,41 @@ impl Handler {
self.callback.lock().unwrap().is_some()
}
fn handle_nonuser_event(&self, wrapper: EventWrapper) {
fn handle_nonuser_event(&self, event: Event<Never>) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
match wrapper {
EventWrapper::StaticEvent(event) => {
callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap())
}
EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback),
}
callback.handle_nonuser_event(event)
}
}
fn handle_user_events(&self) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
callback.handle_user_events(&mut self.control_flow.lock().unwrap());
callback.handle_user_events();
}
}
fn handle_scale_factor_changed_event(
&self,
callback: &mut Box<dyn EventHandler + 'static>,
window: &WinitWindow,
suggested_size: LogicalSize<f64>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor)));
let event = Event::WindowEvent {
window_id: WindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent {
window_id: WindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap());
callback.handle_nonuser_event(event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) {
match proxy {
EventProxy::DpiChangedProxy {
window,
suggested_size,
scale_factor,
} => self.handle_scale_factor_changed_event(
callback,
&window,
suggested_size,
scale_factor,
),
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
}
}
@@ -381,16 +358,18 @@ impl AppState {
/// and can lead to undefined behaviour if the callback is not cleared before the end of
/// its real lifetime.
///
/// All public APIs that take an event callback (`run`, `run_ondemand`,
/// All public APIs that take an event callback (`run`, `run_on_demand`,
/// `pump_events`) _must_ pair a call to `set_callback` with
/// a call to `clear_callback` before returning to avoid undefined behaviour.
pub unsafe fn set_callback<T>(
callback: Weak<Callback<T>>,
window_target: Rc<RootWindowTarget<T>>,
receiver: Rc<mpsc::Receiver<T>>,
) {
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback,
window_target,
receiver,
}));
}
@@ -429,31 +408,36 @@ impl AppState {
HANDLER.set_stop_app_on_redraw_requested(stop_on_redraw);
}
pub fn set_control_flow(control_flow: ControlFlow) {
HANDLER.set_control_flow(control_flow)
}
pub fn control_flow() -> ControlFlow {
HANDLER.control_flow()
}
pub fn exit() -> i32 {
pub fn internal_exit() {
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopExiting));
HANDLER.handle_nonuser_event(Event::LoopExiting);
HANDLER.set_in_callback(false);
HANDLER.exit();
HANDLER.internal_exit();
Self::clear_callback();
if let ControlFlow::ExitWithCode(code) = HANDLER.control_flow() {
code
} else {
0
}
}
pub fn exit() {
HANDLER.exit()
}
pub fn exiting() -> bool {
HANDLER.exiting()
}
pub fn dispatch_init_events() {
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)));
HANDLER.handle_nonuser_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.
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed));
HANDLER.handle_nonuser_event(Event::Resumed);
HANDLER.set_in_callback(false);
}
@@ -541,10 +525,9 @@ impl AppState {
}
}
}
ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
};
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause)));
HANDLER.handle_nonuser_event(Event::NewEvents(cause));
HANDLER.set_in_callback(false);
}
@@ -564,8 +547,10 @@ impl AppState {
// Redraw request might come out of order from the OS.
// -> Don't go back into the callback when our callstack originates from there
if !HANDLER.in_callback.swap(true, Ordering::AcqRel) {
HANDLER
.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
HANDLER.set_in_callback(false);
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events
@@ -576,11 +561,25 @@ impl AppState {
}
}
pub fn queue_event(wrapper: EventWrapper) {
pub fn queue_event(event: Event<Never>) {
if !is_main_thread() {
panic!("Event queued from different thread: {wrapper:#?}");
panic!("Event queued from different thread: {event:#?}");
}
HANDLER.events().push_back(wrapper);
HANDLER.events().push_back(EventWrapper::StaticEvent(event));
}
pub fn queue_static_scale_factor_changed_event(
window: Id<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
HANDLER
.events()
.push_back(EventWrapper::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
});
}
pub fn stop() {
@@ -612,18 +611,35 @@ impl AppState {
HANDLER.set_in_callback(true);
HANDLER.handle_user_events();
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
match event {
EventWrapper::StaticEvent(event) => {
HANDLER.handle_nonuser_event(event);
}
EventWrapper::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
} => {
HANDLER.handle_scale_factor_changed_event(
&window,
suggested_size,
scale_factor,
);
}
}
}
for window_id in HANDLER.should_redraw() {
HANDLER
.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait));
HANDLER.handle_nonuser_event(Event::AboutToWait);
HANDLER.set_in_callback(false);
if HANDLER.should_exit() {
if HANDLER.exiting() {
Self::stop();
}
@@ -634,7 +650,7 @@ impl AppState {
let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events
let app_timeout = match HANDLER.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll | ControlFlow::ExitWithCode(_) => Some(Instant::now()),
ControlFlow::Poll => Some(Instant::now()),
ControlFlow::WaitUntil(instant) => Some(instant),
};
HANDLER

View File

@@ -44,9 +44,6 @@ extern_methods!(
// _mtm: MainThreadMarker,
) -> Option<Id<NSTextInputContext>>;
#[method(visibleRect)]
pub fn visibleRect(&self) -> NSRect;
#[method(hasMarkedText)]
pub fn hasMarkedText(&self) -> bool;

View File

@@ -124,7 +124,7 @@ extern_methods!(
#[method(miniaturize:)]
pub(crate) fn miniaturize(&self, sender: Option<&AnyObject>);
#[method(sender:)]
#[method(deminiaturize:)]
pub(crate) fn deminiaturize(&self, sender: Option<&AnyObject>);
#[method(toggleFullScreen:)]

View File

@@ -5,15 +5,11 @@ use core_foundation::{
data::{CFDataGetBytePtr, CFDataRef},
};
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use smol_str::SmolStr;
use super::appkit::{NSEvent, NSEventModifierFlags};
use super::util::Never;
use super::window::WinitWindow;
use crate::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, Modifiers},
event::{ElementState, KeyEvent, Modifiers},
keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
},
@@ -21,21 +17,6 @@ use crate::{
platform_impl::platform::ffi,
};
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<Never>),
EventProxy(EventProxy),
}
#[derive(Debug)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitWindow>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
@@ -115,7 +96,7 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
let string = ns_event
.charactersIgnoringModifiers()
.map(|s| s.to_string())
.unwrap_or_else(String::new);
.unwrap_or_default();
if string.is_empty() {
// Probably a dead key
let first_char = modifierless_chars.chars().next();
@@ -145,7 +126,7 @@ pub(crate) fn create_key_event(
let characters = ns_event
.characters()
.map(|s| s.to_string())
.unwrap_or_else(String::new);
.unwrap_or_default();
if characters.is_empty() {
None
} else {

View File

@@ -19,14 +19,16 @@ use core_foundation::runloop::{
};
use icrate::Foundation::MainThreadMarker;
use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::NSObjectProtocol;
use objc2::{msg_send_id, ClassType};
use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSWindow};
use crate::{
error::EventLoopError,
event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
event_loop::{
ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget,
},
platform::{macos::ActivationPolicy, pump_events::PumpStatus},
platform_impl::platform::{
app::WinitApplication,
@@ -65,9 +67,10 @@ impl PanicInfo {
}
}
#[derive(Debug)]
pub struct EventLoopWindowTarget<T: 'static> {
pub receiver: mpsc::Receiver<T>,
mtm: MainThreadMarker,
p: PhantomData<T>,
}
impl<T: 'static> EventLoopWindowTarget<T> {
@@ -83,8 +86,38 @@ impl<T: 'static> EventLoopWindowTarget<T> {
}
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty())
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::AppKit(
rwh_06::AppKitDisplayHandle::new(),
))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::set_control_flow(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
AppState::control_flow()
}
pub(crate) fn exit(&self) {
AppState::exit()
}
pub(crate) fn exiting(&self) -> bool {
AppState::exiting()
}
}
@@ -107,14 +140,21 @@ impl<T> EventLoopWindowTarget<T> {
}
pub struct EventLoop<T: 'static> {
/// Store a reference to the application for convenience.
///
/// We intentially don't store `WinitApplication` since we want to have
/// the possiblity of swapping that out at some point.
app: Id<NSApplication>,
/// The delegate is only weakly referenced by NSApplication, so we keep
/// it around here as well.
_delegate: Id<ApplicationDelegate>,
// Event sender and receiver, used for EventLoopProxy.
sender: mpsc::Sender<T>,
receiver: Rc<mpsc::Receiver<T>>,
window_target: Rc<RootWindowTarget<T>>,
panic_info: Rc<PanicInfo>,
mtm: MainThreadMarker,
/// We make sure that the callback closure is dropped during a panic
/// by making the event loop own it.
@@ -147,15 +187,15 @@ impl<T> EventLoop<T> {
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On macOS, `EventLoop` must be created on the main thread!");
.expect("on macOS, `EventLoop` must be created on the main thread!");
// This must be done before `NSApp()` (equivalent to sending
// `sharedApplication`) is called anywhere else, or we'll end up
// with the wrong `NSApplication` class and the wrong thread could
// be marked as main.
let app: Id<WinitApplication> =
let app: Id<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");
}
use NSApplicationActivationPolicy::*;
let activation_policy = match attributes.activation_policy {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
@@ -177,13 +217,17 @@ impl<T> EventLoop<T> {
let (sender, receiver) = mpsc::channel();
Ok(EventLoop {
app,
_delegate: delegate,
sender,
receiver: Rc::new(receiver),
window_target: Rc::new(RootWindowTarget {
p: EventLoopWindowTarget { receiver, mtm },
p: EventLoopWindowTarget {
mtm,
p: PhantomData,
},
_marker: PhantomData,
}),
mtm,
panic_info,
_callback: None,
})
@@ -195,18 +239,18 @@ impl<T> EventLoop<T> {
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootWindowTarget<T>),
{
self.run_ondemand(callback)
self.run_on_demand(callback)
}
// 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_ondemand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootWindowTarget<T>),
{
if AppState::is_running() {
return Err(EventLoopError::AlreadyRunning);
@@ -223,16 +267,14 @@ impl<T> EventLoop<T> {
let callback = unsafe {
mem::transmute::<
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>)>>,
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>)>>,
>(Rc::new(RefCell::new(callback)))
};
self._callback = Some(Rc::clone(&callback));
let exit_code = autoreleasepool(|_| {
let app = NSApplication::shared(self.mtm);
autoreleasepool(|_| {
// A bit of juggling with the callback references to make sure
// that `self.callback` is the only owner of the callback.
let weak_cb: Weak<_> = Rc::downgrade(&callback);
@@ -241,7 +283,11 @@ impl<T> EventLoop<T> {
// # Safety
// We make sure to call `AppState::clear_callback` before returning
unsafe {
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
AppState::set_callback(
weak_cb,
Rc::clone(&self.window_target),
Rc::clone(&self.receiver),
);
}
// catch panics to make sure we can't unwind without clearing the set callback
@@ -257,7 +303,7 @@ impl<T> EventLoop<T> {
debug_assert!(!AppState::is_running());
AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
}
unsafe { app.run() };
unsafe { self.app.run() };
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
@@ -268,7 +314,7 @@ impl<T> EventLoop<T> {
resume_unwind(panic);
}
AppState::exit()
AppState::internal_exit()
}));
// # Safety
@@ -278,22 +324,17 @@ impl<T> EventLoop<T> {
drop(self._callback.take());
AppState::clear_callback();
match catch_result {
Ok(exit_code) => exit_code,
Err(payload) => resume_unwind(payload),
if let Err(payload) = catch_result {
resume_unwind(payload)
}
});
if exit_code == 0 {
Ok(())
} else {
Err(EventLoopError::ExitFailure(exit_code))
}
Ok(())
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootWindowTarget<T>),
{
// # Safety
// We are erasing the lifetime of the application callback here so that we
@@ -306,8 +347,8 @@ impl<T> EventLoop<T> {
let callback = unsafe {
mem::transmute::<
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>)>>,
Rc<RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>)>>,
>(Rc::new(RefCell::new(callback)))
};
@@ -326,7 +367,11 @@ impl<T> EventLoop<T> {
// to ensure that we don't hold on to the callback beyond its (erased)
// lifetime
unsafe {
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
AppState::set_callback(
weak_cb,
Rc::clone(&self.window_target),
Rc::clone(&self.receiver),
);
}
// catch panics to make sure we can't unwind without clearing the set callback
@@ -383,9 +428,9 @@ impl<T> EventLoop<T> {
resume_unwind(panic);
}
if let ControlFlow::ExitWithCode(code) = AppState::control_flow() {
AppState::exit();
PumpStatus::Exit(code)
if AppState::exiting() {
AppState::internal_exit();
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
}

View File

@@ -1,5 +1,3 @@
#![deny(unsafe_op_in_unsafe_fn)]
#[macro_use]
mod util;

View File

@@ -31,7 +31,7 @@ use crate::{
platform::scancode::KeyCodeExtScancode,
platform_impl::platform::{
app_state::AppState,
event::{create_key_event, event_mods, EventWrapper},
event::{create_key_event, event_mods},
util,
window::WinitWindow,
DEVICE_ID,
@@ -212,7 +212,7 @@ declare_class!(
self.removeTrackingRect(tracking_rect);
}
let rect = self.visibleRect();
let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false);
self.state.tracking_rect.set(Some(tracking_rect));
}
@@ -224,7 +224,7 @@ declare_class!(
self.removeTrackingRect(tracking_rect);
}
let rect = self.visibleRect();
let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false);
self.state.tracking_rect.set(Some(tracking_rect));
@@ -826,7 +826,7 @@ impl WinitView {
window_id: self.window_id(),
event,
};
AppState::queue_event(EventWrapper::StaticEvent(event));
AppState::queue_event(event);
}
fn queue_device_event(&self, event: DeviceEvent) {
@@ -834,7 +834,7 @@ impl WinitView {
device_id: DEVICE_ID,
event,
};
AppState::queue_event(EventWrapper::StaticEvent(event));
AppState::queue_event(event);
}
fn scale_factor(&self) -> f64 {
@@ -850,7 +850,7 @@ impl WinitView {
.expect("input context")
.selectedKeyboardInputSource()
.map(|input_source| input_source.to_string())
.unwrap_or_else(String::new)
.unwrap_or_default()
}
pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) {

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