Compare commits

..

227 Commits

Author SHA1 Message Date
Kirill Chibisov
84ef89eb1c Winit version 0.29.0-beta.0 2023-06-30 23:33:29 +04:00
Kirill Chibisov
a320702a71 On X11, fix IME not working
The change to xinput2 completely disabled IME support, thus we've got
a dead keys reporting, because nothing was eating the key events
anymore, however that's not what we really need, given that not
working IME makes it impossible for some users to type.

The proper solution is to not use Xlib at all for that and rely on
xcb and its tooling around the XIM and text compose stuff, so
we'll have full control over what is getting sent to the XIM/IC or not.

Fixes #2888.
2023-06-30 19:59:24 +04:00
dAxpeDDa
b0106898f7 Don't allow event loops to run in parallel 2023-06-29 15:53:58 +02:00
Fredrik Fornwall
924f3323b5 On Web, map bfcache load/unload to suspend/resume 2023-06-28 15:38:49 +02:00
Kirill Chibisov
b97df599c5 On Linux, fix wrong layout for key_without_modifiers
The layout was hardcoded to zero, so the keys were sent for whatever
user configured first.
2023-06-28 14:48:35 +04:00
Kirill Chibisov
0defd747c8 Update xkbcommon-dl to 0.4.0
Most things were simply renamed due to migration to xkeysym.
2023-06-28 14:48:35 +04:00
Kirill Chibisov
e23186db8e Disallow cleanup for TLS in examples
Fixes issue on Wayland due to drop order, since TLS is being dropped
after the event loop, while it shouldn't. In particular it fixes the
crash in the window_run_return example.
2023-06-26 01:04:38 +04:00
Kirill Chibisov
059abb06fc On Wayland, handle none decorations
During the migration some logic wrt `none` decorations was lost along
the way, however we also now try to ask for client side decorations if
the user wants to disable server side decorations.

Fixes #2902.
2023-06-25 14:12:12 +04:00
Josh Groves
bc216b8f67 Allow recreating wasm event loop with spawn (#2897) 2023-06-23 19:31:42 +02:00
Josh Groves
864a1d5924 Fix some typos (#2901) 2023-06-23 10:10:36 +03:00
Kirill Chibisov
05444628e6 Provide a way to set cursor area for IME cursor
Rename `Window::set_ime_position` to `Window::set_ime_cursor_area`
adding a way to create cursor exclusive zone.

Fixes #2886.
2023-06-22 19:12:14 +00:00
Kirill Chibisov
66ff52b012 On Wayland, fix transparency hint not set in new
Fixes #2894.
2023-06-22 00:21:43 +00:00
Robert Bragg
7929999c1c Android: rework keycode handling (#2890)
The recent overhaul of the keyboard API broke keyboard input on Android.

The recent keyboard changes also broke building against the
game-activity backend of android-activity because it was assumed that
the backend is based on the NDK input API which isn't the case with
with game-activity since it doesn't use the InputQueue API from the NDK.

Any alphanumeric keycodes were being mapped to `Unidentified` Keys
which meant even crude keyboard input support was broken.

We do need to expose `getUnicodeChar` (or the ability to look
up characters based on the current character map and modifiers) but
for now we should at least map alphanumeric keycodes to `Key::Character`
for basic interim support of virtual keyboards.

This moves all the keycode mapping into a separate `keycodes.rs` file
to reduce clutter.

This adds back the mapping from Android key codes to Winit key codes
that we had before the keyboard API overhaul.

Android activity does expose scan codes but key codes currently seem
like the more appropriate mapping to Winit physical key codes.

This removes the gnarly, unsafe cfg() guarded digging into
'native-activity' and 'game-activity' specific implementation details. I
never intended to expose these details in the public API and really
hope to avoid there being a release of Winit that would depend on this.

I'm also hoping/considering if I can get away with sealing this without
necessarily requiring a semver breaking release of android_activity
since this absolutely should never have been possible, and can probably
safely assume this was the only code in the wild that has briefly done
this.

I'm also a bit unclear as to what led to doing this. There is a
`.key_code()` and `.scan_code()` getter and we even already accessed the
keycode in the Android backend so I'm not sure how those APIs were missed.
2023-06-21 19:49:44 +02:00
Kirill Chibisov
7094a223af Bring OptionAsAlt back for macOS
The correct handling of this setting requires to change the events
we're getting from the macOS on the fly and call `interpretKeyEvents`,
which could affect handling of the next events, meaning that we can't
provide them on `KeyEvent`.
2023-06-20 19:07:49 +00:00
John Nunley
b2a46d0439 Fill the windows in the examples with a solid color
Fixes #776.
2023-06-19 18:46:38 +00:00
bbb651
4748890935 Add MouseButton::{Back, Forward} to MouseInput
Add named variants for physical back and forward keys which could
be found on some mice. The macOS bits may not work on all the
hardware given that apple doesn't directly support such a thing.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-06-16 08:51:09 +00:00
daxpedda
6300cf915e On Web, implement DeviceEvents (#2871) 2023-06-14 10:26:26 +02:00
daxpedda
9a9c9b15ba Implement ResizeObserver (#2859)
Co-authored-by: Liam Murphy <43807659+Liamolucko@users.noreply.github.com>
2023-06-14 09:43:53 +02:00
dAxpeDDa
7ce86c3d2a Use Window.requestIdleCallback() 2023-06-14 00:03:23 +02:00
daxpedda
a444637b18 Revert "Send modifiers first"
This reverts commit e17977d7c7.
2023-06-14 00:01:37 +02:00
daxpedda
f0d88c52a3 Fix pointer deltas on Firefox and send coalesced events together (#2878) 2023-06-13 15:49:27 +02:00
dAxpeDDa
e17977d7c7 Send modifiers first 2023-06-13 14:33:08 +02:00
dAxpeDDa
a7a8ff0bbb Drop pointerrawupdate support 2023-06-13 10:23:48 +02:00
dAxpeDDa
fc046add78 Resume propagation of events 2023-06-12 00:48:14 +02:00
dAxpeDDa
ab4a4a89e6 Remove unused EventListenerOptions 2023-06-11 19:44:52 +02:00
dAxpeDDa
f7a400ddf6 Improve media queries take 2 2023-06-09 20:38:54 +02:00
Xiaopeng Li
07d39abddd Fix panic when destroying window (#2773)
* Fix crash when destroying window

* Add a comment to draw_rect

---------

Co-authored-by: Xiaopeng Li <lixiaopeng.jetspark@bytedance.com>
2023-06-09 16:20:27 +03:00
Imbris
0c8bf25ae4 Fix compilation on aarch64-linux
aarch64 defines `c_char` as `u8` and not `i8`. Use `c_char`
alias directly.
2023-06-09 11:46:01 +00:00
dAxpeDDa
b5785ba785 Revert "Fix Window::set_inner_size()"
This reverts commit e220a75556.
2023-06-08 09:09:46 +02:00
dAxpeDDa
9797ed86f0 Fix unable to ignore scale factor resize suggestion 2023-06-07 22:46:14 +02:00
dAxpeDDa
e220a75556 Fix Window::set_inner_size() 2023-06-07 14:53:59 +02:00
dAxpeDDa
29d3729ac8 Disallow more methods 2023-06-07 12:53:47 +02:00
Robin Thunström
4a36741f9c On Android, change default implementation to ignore volume keys and let operating system handle them (#2748) 2023-06-06 23:04:51 +02:00
dAxpeDDa
ab46aa5b79 Replace beforeunload with pagehide 2023-06-05 16:40:53 +02:00
dAxpeDDa
12fb37d827 Make media queries more robust 2023-06-05 16:11:22 +02:00
dAxpeDDa
c88a4ab221 Use correct canvas size for scale factor change 2023-06-05 15:39:17 +02:00
daxpedda
8f7f3efc0d On Web, implement Send and Sync where appropriate (#2834) 2023-06-05 02:44:54 +02:00
dAxpeDDa
eb2d3894ef Document unpreventable events 2023-06-05 02:04:37 +02:00
dAxpeDDa
3f4f580181 Add pointerrawupdate support 2023-06-05 02:04:37 +02:00
dAxpeDDa
d3aeff8838 Remove unnecessary preventDefault() calls 2023-06-05 02:04:37 +02:00
dAxpeDDa
0786d534f4 Take IntoIterator in send_events() 2023-06-05 02:04:37 +02:00
dAxpeDDa
b4b2389d0a Split modifier handling in all pointer events 2023-06-05 02:04:37 +02:00
dAxpeDDa
964e342f69 Prevent text selection 2023-06-05 02:04:37 +02:00
dAxpeDDa
a134a59917 Remove MouseEvent fallback support 2023-06-05 02:04:37 +02:00
dAxpeDDa
fbba203c4a Focus window on touch press 2023-06-05 02:04:37 +02:00
dAxpeDDa
61bd8b8254 Send position on button release 2023-06-05 02:04:37 +02:00
dAxpeDDa
587fa67571 Split cursor move handlers 2023-06-05 02:04:37 +02:00
dAxpeDDa
7500a88230 Fix up changelog 2023-06-04 13:45:43 +02:00
dAxpeDDa
82d0380ea6 Ignore pen input on Web 2023-06-04 00:23:47 +02:00
John Nunley
642ce2bfa7 Port to windows-sys v0.48.0 (#2842) 2023-06-04 00:02:37 +02:00
dAxpeDDa
5bbe87960e Replace instant with web-time 2023-06-03 16:05:44 +02:00
Kirill Chibisov
cf77f82ae3 Update remaining actions to v3
This somehow was left unnoticed.
2023-06-03 17:00:55 +03:00
Kirill Chibisov
72cf4e577f Add missing Hash impls on bitflags
Some bitflags in public API lost their `Hash` implementations.

Fixes: 31ebc5caf (Update `bitflags` to `2.0`)
2023-06-03 16:44:52 +03:00
dAxpeDDa
4f3eacf01e On Web, handle coalesced events 2023-06-02 18:48:34 +02:00
George Burton
31ebc5caf4 Update bitflags to 2.0
Co-authored-by: dAxpeDDa <daxpedda@gmail.com>
2023-06-02 17:44:36 +03:00
dAxpeDDa
d273518ce9 Process pointer button events 2023-06-02 12:41:35 +02:00
dAxpeDDa
2ade772ab0 Bump console_log to v1 2023-06-01 17:19:30 +02:00
John Nunley
4ac2006cbc Replace mio with calloop in the X11 backend 2023-05-31 19:44:42 +03:00
dAxpeDDa
ba5ad3be13 On Web, fix no-op for Window::set_fullscreen 2023-05-31 15:25:15 +02:00
dAxpeDDa
8092fa2440 Fix changelog 2023-05-31 13:47:34 +02:00
Toniman575
8bb004a1d9 Rename DeviceEventFilter to DeviceEvents
The use of `Filter` was confusing so it was removed inverting the 
behavior of the enum and methods using it.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-05-30 22:32:31 +03:00
Simon Hausmann
de5327477a web: Fix position of touch events to be relative to the canvas
Use the same logic as for mouse events when not captured.
2023-05-30 21:21:41 +02:00
Nicola Papale
8f959714cc Fix Window::set_theme doc string
Fix #2814.
2023-05-30 12:22:19 +03:00
Kirill Chibisov
035eebb19a Use linux scancode values for KeyCodeExtScancode
Old winit was using linux scancodes, so this should make it backward
compatible with itself.
2023-05-29 13:48:12 +03:00
Kirill Chibisov
b5af6bb266 Use xkbcommon-dl 0.3.0 2023-05-29 00:55:54 +03:00
dAxpeDDa
1805124c54 On Web, wake event loop on request_redraw() 2023-05-28 23:22:08 +02:00
dAxpeDDa
0f64589dba Don't change the internal canvas size 2023-05-28 23:10:33 +02:00
Kirill Chibisov
5438a2a524 Add @daxpedda as web maintainer
Fixes #1777.
2023-05-28 23:48:28 +03:00
Markus Røyset
918430979f Overhaul the Keyboard API
Overhaul the keyboard API in winit to mimic the W3C specification
to achieve better crossplatform parity. The `KeyboardInput` event
is now uses `KeyEvent` which consists of:

  - `physical_key` - a cross platform way to refer to scancodes;
  - `logical_key`  - keysym value, which shows your key respecting the
                     layout;
  - `text`         - the text produced by this keypress;
  - `location`     - the location of the key on the keyboard;
  - `repeat`       - whether the key was produced by the repeat.

And also a `platform_specific` field which encapsulates extra
information on desktop platforms, like key without modifiers
and text with all modifiers.

The `Modifiers` were also slightly reworked as in, the information
whether the left or right modifier is pressed is now also exposed
on platforms where it could be queried reliably. The support was
also added for the web and orbital platforms finishing the API
change.

This change made the `OptionAsAlt` API on macOS redundant thus it
was removed all together.

Co-authored-by: Artúr Kovács <kovacs.artur.barnabas@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
Co-authored-by: daxpedda <daxpedda@gmail.com>
Fixes: #2631.
Fixes: #2055.
Fixes: #2032.
Fixes: #1904.
Fixes: #1810.
Fixes: #1700.
Fixes: #1443.
Fixes: #1343.
Fixes: #1208.
Fixes: #1151.
Fixes: #812.
Fixes: #600.
Fixes: #361.
Fixes: #343.
2023-05-28 21:02:59 +03:00
Kirill Chibisov
f3f46cb3f6 On Wayland, fix Window::set_cursor_visible(true)
Making the cursor back visible was simply forgotten and it was
always hiding instead.

Fixes: 2496098890 (Update wayland-rs to 0.30.0)
Fixes: #2820.
2023-05-26 09:26:22 +03:00
John Nunley
3c3be71a77 Implement PartialOrd/Ord for dpi module types 2023-05-16 05:11:43 +03:00
Kirill Chibisov
a7986b077f Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-05-14 20:36:23 +03:00
Kirill Chibisov
8a0edde5c8 Bump github actions to v3 2023-05-14 20:35:58 +03:00
Kirill Chibisov
a9e168e10d On macOS, fix backspace emission on preedit clear
Fixes: d15feb5cfa (On macOS, fix empty marked text)
2023-05-13 03:02:05 +03:00
Kirill Chibisov
bd9cc2a9da Use cursor-icon crate for CursorIcon
This crate is aimed to simplify handling of cursor icon across
various crates and be used in the public API.
2023-05-09 20:19:35 +03:00
Kirill Chibisov
596c0edf0f Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-05-09 18:28:20 +03:00
Xiaopeng Li
92592ec605 Fix device description leak (#2758)
* Fix device description leak

* Update CHANGELOG.md

---------

Co-authored-by: Xiaopeng Li <lixiaopeng.jetspark@bytedance.com>
2023-05-08 17:58:34 +03:00
Kirill Chibisov
25c4e2e451 On macOS, fix key_up being ignored without IME
Fixes: d15feb5cfa (On macOS, fix empty marked text)
2023-05-06 14:05:28 +03:00
Kirill Chibisov
ad52c72e41 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-05-05 13:27:22 +03:00
Kirill Chibisov
d15feb5cfa On macOS, fix empty marked text blocking input
Fixes #2775.
2023-05-04 00:24:02 +03:00
Kirill Chibisov
9938327066 On Wayland, fix nightly warnings
The new analysis suggests that we can remove mut.
2023-05-04 00:24:02 +03:00
Kirill Chibisov
f980ed7b83 On X11, fix nightly warnings
The new analysis suggests that we can remove mut.
2023-05-04 00:24:02 +03:00
Kirill Chibisov
2496098890 Update wayland-rs to 0.30.0
This update rewrites the winit's Wayland backend using new wayland-rs
0.30 API. This fixes long standing issue with the forward compatibility
of the wayland backend, meaning that future updates to the wayland
protocol won't break rust code anymore. like it was before when adding
new shm/enum variants into the protocol.

Fixes #2560.
Fixes #2164.
Fixes #2128.
Fixes #1760.
Fixes #725.
2023-04-19 00:56:29 +03:00
Amandus Søve Thorsrud
60e91b187a Run Window::set_ime_position on main thread on macOS
Fixes #2756.
2023-04-15 02:58:36 +03:00
Xiaopeng Li
2486f0f1a1 Fix potential panic (#2755)
* Fix potential panic

* Update CHANGELOG.md

* Use checked_div

---------

Co-authored-by: Xiaopeng Li <lixiaopeng.jetspark@bytedance.com>
2023-04-03 21:46:09 +03:00
Emil Ernerfeldt
fbea75d31f Add cargo-deny check to CI 2023-03-16 23:05:41 +03:00
Kirill Chibisov
d4c9535af9 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2023-03-16 23:05:17 +03:00
daxpedda
f0fcb346b0 On Web, use target_family = "wasm" 2023-03-16 22:49:59 +03:00
Mads Marquart
77f8e511e9 Fix macos memory leaks (#2739)
* Use a weak reference from WinitView to WinitWindow

* Allow patched objc2 version

* Add changelog entry
2023-03-14 13:27:41 +03:00
Emil Ernerfeldt
3217eaa416 Fix 1.68 clippy warnings 2023-03-12 20:02:49 +03:00
Kirill Chibisov
b18295a1ce Bump MSRV to 1.64 2023-03-08 19:34:10 +03:00
esdevver
fb9695d56d Changed 'an' to 'a' in documentation (#2715) 2023-03-04 10:37:15 +01:00
Kirill Chibisov
08bdca19b1 Bump version on master
This commit does not represent a release and only
synchronizes CHANGELOG from the latest release.
2023-03-02 17:15:48 +03:00
Dylan Scott
79ac236721 On macOS, resize simple fullscreen on window move
Fixes #1118.
2023-03-02 01:47:45 +03:00
Nicolas Mazzon
b870a11a99 On Windows, check whether CoCreateInstance succeeds 2023-03-02 01:24:04 +03:00
Kirill Chibisov
2af1550bbb On macOS, fix initial focused state
The synthetic focused event was queued after the real event was send
leading to focused issues on startup.

Fixes #2695.
2023-02-27 20:46:00 +03:00
John Nunley
ed796dcd15 Update FEATURES.md 2023-02-26 09:53:45 +03:00
Kirill Chibisov
a006cd7dc8 On Wayland, fix rounding issue in resizes 2023-02-21 11:44:22 +03:00
Simon Hausmann
a31f71ee07 Add support for Window::theme on the web (#2687) 2023-02-20 08:51:21 +01:00
Kirill Chibisov
0f89aac9f6 On Wayland, fix rare crash on DPI change
While I don't understand the root cause for this issue, we can
dirty fix like that for now.
2023-02-19 17:39:39 +03:00
Kirill Chibisov
82df9531f4 On macOS, set resize increments only for live resize
Closes #2684 for macOS.
2023-02-15 03:32:55 +03:00
Sludge
265152355e Implement HasRawDisplayHandle for EventLoop (#2677)
* Implement `HasRawDisplayHandle` for `EventLoop`

* Add changelog entry
2023-02-10 16:25:22 +01:00
John Nunley
37c0f615cf On Windows, name the waiter thread (#2672) 2023-02-08 21:39:01 +01:00
John Nunley
5ba6bdef49 Replace lazy window message ids with a slimmer version (#2598) 2023-02-04 15:38:21 +01:00
Kirill Chibisov
69d6076310 Bump version on master
This commit does not represent a release and only
synchronizes CHANGELOG from the latest release.
2023-02-04 14:47:31 +03:00
Kirill Chibisov
7029ce6ecd Fix window drop on Wayland
In some scenarious of window dropping the callback for keyboard
may run after the window was dropped.
2023-02-02 14:42:34 +03:00
Kirill Chibisov
1eb1a13a77 Bump version on master
This commit does not represent a release and only
synchronizes CHANGELOG from the latest release.
2023-02-02 10:55:59 +03:00
Kirill Chibisov
3fd73848dd On macOS, fix Ime::Commit persisting
This commit clears the currently marked text on `Ime::Commit`, so
normal `ReceivedCharacter` input can continue.
2023-02-01 18:08:25 +03:00
Samuel
4e1c46fe9e Windows: Fix Alt key press entering menu loop (#2665) 2023-02-01 12:03:58 +01:00
Jack Wright
180a4c7a16 Add WindowExtMacOS::{set_,}option_as_alt
This adds an ability to control left and right `Option` keys to be
treated as `Alt`, thus not producing diacritical marks.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-01-31 12:35:49 +03:00
Kirill Chibisov
13613931cf Implement serde ser/deser for Theme 2023-01-31 12:14:15 +03:00
Diggory Hardy
483c1d40ae Properly print outputs in monitor_list example 2023-01-30 14:17:41 +03:00
Lukas Lihotzki
1b4045dcb2 Add Window::set_ime_purpose
This adds a way to set the purpose for the IME input, implemented
only on Wayland for now.
2023-01-29 18:46:46 +03:00
Kirill Chibisov
8f8da0f8bb Fix rerun-if-changed emmiting from build.rs
The docs state that it accepts `PATH`, but not like the env variable.
So to make it work each `PATH` should be emmited from each `println!`.

Fixes #2657.
2023-01-29 14:23:45 +03:00
Markus Siglreithmaier
23b821285c On Windows, fix window size for maximized, undecorated windows (#2584)
Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
2023-01-28 14:04:47 +01:00
Diggory Hardy
c984476687 Clarify Window::set_decorations/is_decorated behaviour 2023-01-28 10:50:34 +03:00
Andrea Pessino
42c395e49d Fixed visibility/activation issues on Windows. (#2656) 2023-01-27 23:01:41 +01:00
Shane Pearman
422c6b1987 Allow introspection of WindowBuilder attributes
Makes WindowAttributes public and adds window_attributes() getter to
WindowBuilder.

In version 0.27, the WindowAttributes struct was made private, but this
removed the ability to introspect the default WindowBuilder values.
2023-01-27 08:38:56 +03:00
Amr Bashir
b457329003 Add WindowBuilder::with_active
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-01-27 08:08:29 +03:00
Kirill Chibisov
930df0ec45 Fix clippy issues on stable 2023-01-27 07:18:58 +03:00
Markus Siglreithmaier
e1b7fda409 Bump windows-sys version to 0.45 (#2639)
Bump windows-sys version to fix regression
2023-01-23 22:10:28 +01:00
Francesca Lovebloom
e423802ed3 Remove francesca64 from CODEOWNERS (#2644) 2023-01-23 09:56:09 +01:00
Mads Marquart
a82f66826b Use a bit less unsafe on iOS (#2643)
* Use a bit less `unsafe` on iOS

I did test this in XCode 11.3's "Debug View Heirarchy", the NSStringRust problem is no longer applicable (likely because Rust got better at emitting correct debug info).

* Avoid using `id` on iOS
2023-01-23 00:01:45 +01:00
Mads Marquart
0f2fbe373b Simplify event queuing on macOS (#2642) 2023-01-22 23:29:38 +01:00
Mads Marquart
7341ee80ea Note the status quo on RedrawRequested (#2641)
And link to https://github.com/rust-windowing/winit/issues/2640
2023-01-21 18:56:58 +01:00
Jim Eckerlein
d448d3e14f Add smart magnify gesture support for macOS (#2554)
* Add smart magnification gesture

* Deliver position of smart magnification event

* Document smart magnification event

* Revert "Deliver position of smart magnification event"

This reverts commit ac0e61a9a4.

* Remove mention of touchpad from smart magnification event

* Update change log

* Mention minimum macOS version supporting smart magnification

* Improve doc
2023-01-21 17:35:07 +01:00
Andreas Reich
a867032e1e [MacOS] Fix deadlock on maximizing window from event callback (#2636) 2023-01-21 17:29:29 +01:00
Douglas Dwyer
b711a11549 Properly remove window mouse event listeners (#2632)
* Properly remove window mouse event listeners

* Update CHANGELOG.md

* Fix formatting

Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-01-21 16:58:05 +01:00
Amr Bashir
809162fbd0 Add Window::is_minimized
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
2023-01-20 00:39:04 +03:00
Kirill Chibisov
de782504ab On Wayland, add support for fractional scaling
This adds support for the fractional scaling on Wayland via the
wp-fractional-scale protocol.

Co-authored-by: Julian Orth <ju.orth@gmail.com>
2023-01-20 00:02:16 +03:00
Roman Akberov
1886949efe On macOS, fix middle/other mouse buttons reporting
All buttons except for the left/right/middle was always reported
as middle.
2023-01-18 06:32:34 +03:00
Kirill Chibisov
b1a5fae1f5 On X11, fix errors bleeding from hooks handling them
This commit fixes it, by not updating the `latest_error` when
any of the hooks handled the error, otherwise it'd interfere
with the winit's error checking.
2023-01-18 05:58:09 +03:00
Amr Bashir
a88d2e079d On Windows and MacOS, add Window::has_focus 2023-01-17 04:30:14 +03:00
Amr Bashir
067535eb38 Fix Window::set_minimized(false) on Windows
When other application minimized the winit window the
minimize state was going out of sync. This commit fixes
it by polling the state in `set_minimized`.
2023-01-17 03:22:52 +03:00
John Nunley
7d626d9dfd Add a function for waiting on a Duration 2023-01-16 04:14:09 +03:00
Kirill Chibisov
62ce14a013 Add Window::set_transparent
Provide a hint to system compositor whether the window is transparent or
not. Only implemented on macOS and Wayland for now.
2023-01-15 23:39:36 +03:00
Mads Marquart
6f60c7a6cc Note the macOS and Windows versions that winit supports 2023-01-15 23:18:23 +03:00
Turki Jamaan
6cf0bf76da iOS: fix accidentally flipped assertion (#2629)
* iOS: fix accidentally flipped assertion

* No need to update the changelog
2023-01-13 23:40:24 +01:00
Michael Murphy
9225b2812e feat(x11): Add Window::drag_resize_window (#2515) 2023-01-11 10:07:09 -07:00
John Nunley
08ce3af3e1 Ensure all free unixes can build
This fixes the `cfg` guards inside the `Cargo.toml`.
2023-01-10 12:00:28 +03:00
John Nunley
490abcad14 Remove xlib_xconnection from public interface 2023-01-10 11:46:48 +03:00
John Nunley
4b22ca8daf chore: Alphabetize dependencies (#2619) 2023-01-06 11:26:57 +01:00
Jeremy Soller
66ca445caa Redox OS support (#2588)
* Add Redox OS support

* Simplify control flow usage

* Apply more recommendations

* Update naming to indicate that Orbital is a platform

* Adjust import order
2023-01-05 06:58:08 -07:00
Kirill Chibisov
2f52c23fa9 Fix RedrawRequested not emitted on Wayland in resize
Fixes #2609.
2022-12-29 21:06:46 +03:00
Mads Marquart
ee88e38f13 Reduce amount of unsafe on iOS (#2579)
* Use objc2::foundation CG types

* Add safe abstraction over UIApplication

* Add safe abstraction over UIDevice

* Add safe abstraction over UIScreen

* Add safe abstraction over UIWindow

* Add safe abstraction over UIViewController

* Add safe abstraction over UIView

* Appease clippy
2022-12-28 18:36:32 +01:00
Amr Bashir
5e77d70245 Use cfg aliases throught the code base
Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-12-25 10:57:27 +03:00
Alphyr
58ec458877 Remove compatibility shim for raw-window-handle 0.4 2022-12-24 15:53:46 +03:00
Miguel Medina Ballesteros
94e4c394e7 Swap assert by debug_assert for recovereable issue. Fixes #2597 (#2599)
* Swap assert by debug_assert for recovereable issue

* Remove debug assert completely
2022-12-23 14:19:25 +01:00
Ryo Hirayama
f43ce2a131 Web touch event (#2188)
* feat: add pointer events to web

* feat: remove PointerType for touch events

* Remove duplicate

* Changelog and features

* Remove PointerType

* feat: renamed events, added touch type guard

* Rename

* Flip the y axis

* Fix physical position and add force

* Update comment

* Update features

* Use normalized force

* Remove unnecessary todos

* Update comment

* Refactor add touch_handler

* Rephrase by Liamolucko

* Update CHANGELOG.md

* Fix duplicate mouse and touch events

* Removed workaround for scale factor

* Flip the y axis

* Fix

* Fmt

* Replace `match` with a single pattern with `if let`

* Update documentation

* Have one callback per event

* Remove a comment

* Fix

* Remove y-axis flip

* Update src/event.rs

Co-authored-by: Mads Marquart <mads@marquart.dk>

* Fix platform specific comment

* Fix extra argument to `touch_position` function

Co-authored-by: Dany Sluijk <me@dany.dev>
Co-authored-by: Johan Klokkhammer Helsing <johanhelsing@gmail.com>
Co-authored-by: oscrim <oscar@widefind.se>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-12-23 06:55:22 +01:00
Amr Bashir
402cbd55f9 fix unnecessary cast lint (#2596)
* fix clippy lints on Windows

* fix lints on other platforms

* a couple more

* again

* don't know what's goging on anymore

* fix examples

* comon

* how about now?

* this is getting annoying

* hmmm

* explicitly set a type

* 😢

* don't cast on x64 targets

* apply code review requests

* fix attributes on expressions

* fix ios
2022-12-22 20:35:33 +01:00
Ngo Iok Ui (Wu Yu Wei)
da7422c6e1 Add WindowBuilder::with_parent_window (#2548)
* On macOS, add `WindowBuilderExtMacOS::with_parent_window`

* Replace Parent with Option<Id<NSWindow, Shared>>

* Add addChildWindow method on NSWindow instead

* Update with_parent_window to be unsafe fn

* Add unified `with_parent_window`

* Remove `WindowBuilderExtUnix::with_parent`

* Remove `WindowBuilderExtWindows::with_parent_window`

* Clean up CI warnings

* Update CHANGELOG.md

It's `WindowBuilderExtX11` rather than `WindowBuilderExtUnix`

* Rename parent to owner

* Make with_parent_window unsafe and update its doc

* Add another way to get window on mac

* Add more documentations

* Add match arm and panic on invalid varients

* Add Xcb arm

* Update child_window example to make it safer and work in i686

* Remove duplicate entry in CHANGELOG.md

* Propogate error instead of expect

* Replace unreachable to panic

* Add platform note to X11

Co-authored-by: Wu Yu Wei <wusyong9104@gmail.com>
2022-12-22 01:07:13 +01:00
Amr Bashir
8934d2765d Add missing closing parentheses (#2587) 2022-12-11 03:29:19 +01:00
Amr Bashir
89eea64a4a Retain WS_MAXIMZE when unminmizing a maximized window (#2581)
Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
2022-12-09 19:33:11 +01:00
feelingnothing
9f781bc422 On Windows, fix left mouse button release event not being sent after Window::drag_window (#2564) 2022-12-06 23:18:50 +01:00
Mads Marquart
4ed4e918f3 Clean up UIView override declaration (#2578) 2022-12-01 09:32:44 +01:00
Mads Marquart
2e4d79f57a Do fullscreen logic synchronously on main thread (#2575) 2022-11-30 14:49:18 +01:00
Mads Marquart
bf92f3e97b macOS: Run tasks synchronously on main thread instead of asynchronously (#2574)
* Close windows synchronously on main thread

* Set style mask synchronously on main thread

* Set title synchronously on main thread

* Set visibility and focus synchronously on main thread

* Set window level synchronously on main thread

* Set position and size synchronously on main thread

* Set cursor hittest synchronously on main thread

* Add changelog entry
2022-11-30 14:30:32 +01:00
Mads Marquart
2a58b785fe Refactor SharedState so that it is no longer behind an Arc (#2573)
* Refactor SharedState so that it is no longer behind an Arc

* Always use `Window::lock_shared_state`
2022-11-29 12:58:35 +01:00
Xiaopeng Li
32784af3c4 Don't panic when getting refresh rate failed (#2533)
This fixes a crash on macOS when trying to get the monitor
refresh rate from the disabled monitor.

Co-authored-by: Jet Spark <lixiaopeng.jetspark@bytedance.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-11-29 11:28:46 +01:00
Amr Bashir
94688a62f0 On Windows and macOS, add API to enable/disable window controls (#2537)
* On Windows and macOS, add API to enable/disable window controls

* fix build

* missing import

* use `WindowButtons` flags

* rename to `[set_]enabled_buttons`

* add example, fix windows impl for minimize

* macOS: Fix button enabling close/minimize while disabling maximized

* Update src/platform_impl/windows/window.rs

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>

* compose the flags on a sep line, use `bool::then`

Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2022-11-29 11:03:51 +01:00
Amr Bashir
28e34c2e1b Add Window::set_theme (#2553)
* Add `Window::set_theme`

* typo

* fix linux build

* fix wayland

* review changes

* update docs

* update changelog

* pin `image` dep

* suppport falling back to system default

* fix linux

* default to dark on macOS and x11

* fix `setAppearance` definition

* add macOS notes

* update docs

* Update CHANGELOG.md

Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>

* update doc

* Revert "pin `image` dep"

This reverts commit 7517f7c506.

* Update theme example with Window::set_theme

* Fix Window::theme getter on macOS

Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-11-29 10:05:51 +01:00
Fotis Gimian
9ae7498a8a On Windows, revert window background to an empty brush to avoid white flashes when changing scaling (#2571) 2022-11-27 22:28:14 +01:00
Robert Bragg
1786c877ec android: depend on android-activity 0.4.0 (#2557) 2022-11-26 11:54:08 +01:00
Amr Bashir
101ac8908c Add Window::set_window_level API
This adds `Window::set_window_level` to control the preferred
z level of the window.

Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-11-26 04:50:58 +03:00
Marijn Suijten
ba4bf03675 ci: Specify to build winit package when invoking cargo-apk (#2561)
* ci: Don't use `$CMD` for Android doc building

Since migrating `cargo-apk` to `clap` [it is now annoying] to pass
unknown arguments to an underlying `cargo` command (like `cargo doc`):
fortunately generating docs doesn't need to go through `cargo apk` to
set up cross-compiler/linker environment variables at all.

[it is now annoying]: https://github.com/rust-windowing/android-ndk-rs/pull/363

* ci: Simplify

* ci: Explicitly build just the `winit` package on Android

Since https://github.com/dvc94ch/cargo-subcommand/pull/23 `cargo-apk`
now strictly searches for workspaces first before committing to finding
the right package _within said workspace_, and bails when no package was
selected since we don't support selecting (building, packaging, running)
>1 target currently.

Perhaps it's a bit hash to enforce this on free-form `cargo apk --`
invocations, but it is what it is.
2022-11-24 21:49:47 +01:00
Mads Marquart
a63b066ed5 Fix MouseButton::Other value on Windows (#2565)
This was a mistake in the transition to windows-sys: https://github.com/rust-windowing/winit/pull/2057

We used winapi's GET_XBUTTON_WPARAM before which is using HIWORD instead of LOWORD: https://docs.rs/winapi/0.3.9/src/winapi/um/winuser.rs.html#1297-1299
2022-11-23 16:43:56 +01:00
Amr Bashir
f77f858e9b On macOS, add documentEdited APIs (#2550)
* On macOS, add documentEdited APIs

Port of 33fdeab629

* Update src/platform/macos.rs

Co-authored-by: Mads Marquart <mads@marquart.dk>

* typo

Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-11-23 16:07:41 +01:00
Mads Marquart
6d0cf6a275 Remove WindowBuilderExtIOS::with_root_view_class (#2459) 2022-11-23 15:53:06 +01:00
Mads Marquart
12df8b6c0c macOS: Fix ApplicationDelegate::init (#2566) 2022-11-23 15:23:06 +01:00
Amr Bashir
65baae75c4 Add Window::set_content_protected on macOS and Windows (#2525)
* Add `Window::set_content_protect` on macOS and Windows

* Update window.rs

* Add builder variant

* fix import

* fix argument type

* fix import

* fix always visible window on Windows

* update docs
2022-11-23 14:51:34 +01:00
Amr Bashir
418cc44e93 On macOS, add EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps (#2551)
* On macOS,  add `EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps`

* Update src/platform/macos.rs

Co-authored-by: Mads Marquart <mads@marquart.dk>

* remove todo

Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-11-23 13:42:46 +01:00
Mads Marquart
ce6c6e8c95 Only build, but don't run tests in MSRV CI (#2558)
* Only build, but don't run tests in MSRV CI

Since the MSRV of development dependencies can easily be bumped without it affecting the MSRV of the published version of `winit`

* Run clippy on stable Rust instead of MSRV Rust

clippy inspects the `rust-version` field, and only suggests changes that conform to that.
2022-11-23 13:07:58 +01:00
Mads Marquart
bdcbd7d1f9 macOS: Fix NSWindowLevel values (#2545)
* Fix NSWindowLevel values

* Fix formatting
2022-11-22 11:08:56 +01:00
Robert Bragg
05484c5888 Android: rework backend to use android-activity crate (#2444)
This updates the Android backend to use the android-activity crate instead
of ndk-glue. This solves a few issues:
1. The backend is agnostic of the application's choice of Activity base
   class
2. Winit is no longer responsible for handling any Java synchronization
   details, since these are encapsulated by the design of
   android_activity
3. The backend no longer depends on global / static getters for state
   such as the native_window() which puts it in a better position to
   support running multiple activities within a single Android process.
4. Redraw requests are flagged, not queued, in a way that avoids taking
   priority over user events (resolves #2299)

To make it possible for application crates to avoid explicitly
depending on the `android-activity` crate (and avoid version conflicts)
this re-exports the android-activity crate under:

  `winit::platform::android::activity::*`

This also adds `android-native-activity` and `android-game-activity`
features that set the corresponding android-activity features.

Addresses: PR https://github.com/rust-windowing/winit/pull/1892
Addresses: PR https://github.com/rust-windowing/winit/pull/2307
Addresses: PR https://github.com/rust-windowing/winit/pull/2343

Addresses: #2293
Resolves: #2299

Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>

Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
2022-11-10 17:55:19 +01:00
Amr Bashir
50bbc85dc3 On Windows, fix icons specified on WindowBuilder not taking effect for windows created after the first one (#2530) 2022-11-06 21:30:55 +01:00
Xiaopeng Li
8669c2e8df On macOS, fix panic in current_monitor_inner 2022-11-05 05:30:39 +03:00
Mads Marquart
97d4c7b303 macOS: Fix overridden fullscreen selectors (#2546) 2022-11-03 22:33:38 +01:00
Amr Bashir
08f9e374e0 Add Window::title getter on Windows and macOS 2022-11-03 20:11:37 +03:00
Ihor Ranchynskyi
a7a7cc64cd Bump windows-sys to 0.42 (#2540) 2022-10-31 23:19:45 +01:00
Kirill Chibisov
04d9e081b8 Bump version on master
This commit does not represent a release and only
synchronizes CHANGELOG from the latest release.
2022-10-26 18:20:58 +03:00
i509VCB
8fc24c959a Generate docs.rs documentation with doc_auto_cfg (#2358) 2022-10-25 00:02:52 +02:00
Amr Bashir
2fb15dbe8a Fix menubar focus using Alt on Windows (#2521) 2022-10-20 17:59:12 +02:00
keiya sasaki
92fdf5ba85 Rework theme API
This commit adds support for theming on macOS and
also unifies the system theme handling across platforms.
2022-10-18 21:34:36 +03:00
Kirill Chibisov
4f06cfcf5b On Wayland, fix invalid offsets being sent in Preedit
Even when the protocol explicitly tells to send proper UTF-8
boundaries for cursor, some IMEs don't do that, so sanity check
them before sending downstream.
2022-10-18 17:13:31 +03:00
Kirill Chibisov
462bb4d324 Bump version on master
This commit does not represent a release and only
synchronizes CHANGELOG from the latest release.
2022-10-15 09:30:25 +03:00
Kirill Chibisov
f6ca8515ab On X11, fix IME crashing during reload
During reload we were picking old styles, but the styles could
change during reload leading to errors during IME building.

Fixes #2510.
2022-10-10 00:13:37 +03:00
Shinichi Tanaka
71094e5703 On X11, allow building window with parent 2022-10-09 23:12:23 +03:00
Lucas Kent
bb0f965c57 Update cargo-run-wasm (#2509) 2022-10-09 04:49:19 +02:00
Markus Siglreithmaier
4d48c76da9 Windows, emit ReceivedCharacter on system keybinds
Currently needed for downstream users relaying on `ReceivedCharacter` for implementing
keybindings.
2022-10-08 06:32:40 +03:00
Mads Marquart
fafdedfb7d Simplify internal type construction 2022-09-21 11:04:28 +03:00
killian
25b129362f On Windows, fixed focus event emission on minimize. 2022-09-20 20:26:37 +02:00
Mads Marquart
48b843e42d Accepts first mouse (#2457)
* MacOS: set value for `accepts_first_mouse`

* Update CHANGELOG and FEATURES

* Field doesn't need to be public

* Convert `bool` to `BOOL`

* Fix formatting

* Move flag from window state to view instance

* Feedback from PR

* Fix changelog location
2022-09-13 21:11:18 +02:00
Kirill Chibisov
58f2455aa9 Bump version on master
This commit does not represent a release and only
synchronizes CHANGELOG from the latest release.
2022-09-12 18:15:30 +03:00
Kirill Chibisov
155f1f9720 On X11 query for XIM styles before creating IME
Fixes #2448.
2022-09-11 19:36:56 +03:00
Kirill Chibisov
3b56b0e76f Add release process
This should maintainers to handle releases and
establish a non-blocking workflow.

Fixes #2454.
2022-09-11 16:20:09 +03:00
Kirill Chibisov
5d2aca90bd Send empty Ime::Preedit before the Ime::Commit
This should help downstream to automatically clear it.
2022-09-11 00:48:24 +03:00
Kirill Chibisov
ba49db2cb9 Remove automatic publish script
This script is confusing and provides no value especially
with release branches and patch fixes.
2022-09-09 19:01:05 +03:00
Kirill Chibisov
a4695c5397 Specify minimum supported version for RWH 0.4
Winit uses raw-window-handle of version 0.4.3,
but only 0.4.0 was specified.
2022-09-09 12:11:55 +03:00
Weng Xuetian
92ddb3483e Clear preedit if there is no pending preedit on Wayland
Fixes #2478.
2022-09-09 10:53:58 +03:00
Mads Marquart
fec52b028e Fix runloop entry (#2480)
Introduced in https://github.com/rust-windowing/winit/pull/2479; turns out the definitions were not entirely equal, the `kCFRunLoopEntry` that we were using previously was defined as `0` while the correct value is `1` (which meant the `unimplemented!()` branch suddenly started triggering)
2022-09-08 21:56:53 +02:00
Mads Marquart
d8c0ee733b Remove custom definition of Core Foundation runloop functionality (#2479)
Use the definitions that `core_foundation` exposes (almost the same, except `CFRunLoopSourceContext::perform` is not nullable, so we account for that as well).
2022-09-08 21:03:25 +02:00
Mads Marquart
fb248eaadc Clean up iOS class declaration (#2462)
* Begin abstraction over UIKit

* Clean up UIWindow override declaration

* Clean up UIApplication delegate declaration

* Clean up UIViewController override declaration

* Finalize objc -> objc2 rename
2022-09-08 20:30:34 +02:00
Mads Marquart
da7bf8e29b macOS: Fix WindowBuilder::with_resize_increments (#2477)
Introduced in https://github.com/rust-windowing/winit/pull/2411
2022-09-08 18:54:22 +02:00
shuo
a6a8b12537 Update Readme for iOS platform specific info (#2473)
* Update Readme for iOS platform specific info

* Update README.md

Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-09-08 16:52:57 +02:00
Mads Marquart
340f951d10 Refactor macOS to use new objc2 features (#2465)
* Remove UnownedWindow::inner_rect

* Refactor custom view to use much less `unsafe`

The compiler fence is safe to get rid of now since `interpretKeyEvents` takes `&mut self`

* Refactor Window to use much less unsafe

* Refactor NSApplication usage to have much less unsafe

* Remove cocoa dependency

* Enable `deny(unsafe_op_in_unsafe_fn)` on macOS

Also re-enable clippy `let_unit_value` lint

* Remove #[macro_use] on macOS

* Refactor window delegate to use much less unsafe
2022-09-08 16:45:29 +02:00
Marijn Suijten
05dd31b8ea Revert "ci: manually point ANDROID_NDK_ROOT to latest supplied version"
This reverts commit 4895a29e92.

GitHub Actions' runner-images readded this environment variable on my
request [1] as it wasn't strictly related to the deprecated and removed
`ndk-bundle` NDK release.  Back out of the workaround to keep CI scripts
tidy.

[1]: https://github.com/actions/runner-images/issues/5879#issuecomment-1197811704
2022-09-06 16:48:28 +03:00
Lucas Kent
0fca8b088d WindowBuilderExtWebSys::with_prevent_default disables scrolling on both mobile and desktop (previously just desktop) (#2216)
* Disable scrolling on web by default but provide method in builder to enable it

* rename enable_web_scroll -> enable_web_page_scroll

* move enable_web_page_scroll into prevent_default option

* final approach

* Mark prevent_default change as breaking

Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-09-04 05:45:30 +02:00
Anton Bulakh
ab56e9f57d Allow changing resize increments after window creation 2022-09-03 21:50:22 +03:00
Lucas Kent
97d2aaa953 Add web_aspect_ratio example (#2209)
* Add web_aspect_ratio example

* Review feedback
2022-09-03 18:26:24 +02:00
Mads Marquart
29419d6c38 Refactor macOS cursor code (#2463) 2022-09-02 21:02:40 +02:00
Mads Marquart
e517e468f8 Fix declare_class! indentation (#2461)
* Fix NSWindow delegate indentation

* Fix NSView delegate indentation
2022-09-02 19:38:32 +02:00
Mads Marquart
d67c928120 Clean up macOS class declaration (#2458)
* Begin abstraction over AppKit

* Clean up NSApplication delegate declaration

* Clean up NSApplication override declaration

* Clean up NSWindow delegate declaration

* Clean up NSWindow override declaration

* Clean up NSView delegate declaration
2022-09-02 18:46:18 +02:00
Mads Marquart
112965b4ff Initial transition to objc2 (#2452)
* Use objc2

* Use objc2's NSInteger/NSUInteger/NSRange
2022-09-02 15:48:02 +02:00
Mads Marquart
e0018d0710 Bump MSRV to 1.60 (#2453) 2022-09-02 10:59:05 +02:00
Mads Marquart
1ca8b65e85 Split platform::unix into platform::x11 and platform::wayland
This also removes deprecated `WindowExtUnix::is_ready`.
2022-09-01 08:05:32 +03:00
Mads Marquart
a43a15b4a0 iOS: Fix a few instances of UB (#2428)
* Fix iOS 32-bit

* Fix a few invalid message sends on iOS
2022-09-01 03:10:00 +02:00
Mads Marquart
66aa6c945d Add myself as iOS co-maintainer (#2451) 2022-09-01 01:45:11 +02:00
ajtribick
dfecdc5762 Windows: Update handling of system keypresses (#2445)
- Pass WM_SYSKEYDOWN to DefWindowProc
- Avoid intercepting WM_SYSCHAR to allow ALT+Space to work: removes ReceivedCharacter events for alt+keypress
- Intercept WM_MENUCHAR to disable bell sound
2022-09-01 00:03:48 +02:00
Mads Marquart
8729119536 Remove parking_lot dependency (#2423) 2022-08-31 18:32:19 +02:00
daxpedda
ec7e935248 Document WindowEvent::Moved OS support (#2442) 2022-08-31 06:57:37 +02:00
ajtribick
fd72000a9a Disable default features in simple_logger
This fix CI building due to implicit rust version
bump in examples.
2022-08-29 00:26:51 +03:00
Alex Butler
e91ee811cb Use sctk-adwaita 0.5.1 auto theme selection
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2022-08-24 23:43:36 +03:00
Kirill Chibisov
b3b80166ce Mark new events as breaking change
Adding a new enum variant is a breaking change in winit.
2022-08-24 16:33:58 +03:00
daxpedda
f9b41fd819 Update sctk-adwaita to use ab_glyph
The crossfont will still be available under the option.
2022-08-24 15:24:29 +03:00
Sludge
0d9c39029c Document WindowEvent::Moved as unsupported on Wayland 2022-08-20 00:32:40 +03:00
Joonas Satka
da2cef97a3 Add touchpad magnify and rotate gestures support for macOS (#2157)
* Add touchpad magnify support for macOS

* Add touchpad rotate support for macOS

* Add macOS rotate and magnify gesture cancelled phases

* Correct docs for TouchpadRotate event

* Fix tracing macros
2022-08-16 17:20:06 +02:00
Markus Siglreithmaier
76f158d310 On Windows, improve support for undecorated windows (#2419) 2022-08-15 02:36:37 +02:00
224 changed files with 27177 additions and 14190 deletions

17
.github/CODEOWNERS vendored
View File

@@ -9,17 +9,18 @@
/src/platform_impl/android @msiglreith
# iOS
/src/platform/ios.rs @francesca64
/src/platform_impl/ios @francesca64
/src/platform/ios.rs @madsmtm
/src/platform_impl/ios @madsmtm
# Unix in general
/src/platform/unix.rs @kchibisov
# Unix
/src/platform_impl/linux/mod.rs @kchibisov
# Wayland
/src/platform/wayland.rs @kchibisov
/src/platform_impl/linux/wayland @kchibisov
# X11
/src/platform/x11.rs @kchibisov
/src/platform_impl/linux/x11 @kchibisov
# macOS
@@ -27,9 +28,13 @@
/src/platform_impl/macos @madsmtm
# Web (no maintainer)
/src/platform/web.rs
/src/platform_impl/web
/src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda
# Windows
/src/platform/windows.rs @msiglreith
/src/platform_impl/windows @msiglreith
# Orbital (Redox OS)
/src/platform/orbital.rs @jackpot51
/src/platform_impl/orbital @jackpot51

View File

@@ -9,20 +9,50 @@ jobs:
Check_Formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Check Formatting
run: cargo +stable fmt --all -- --check
cargo-deny:
name: cargo-deny
# 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
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.57.0, stable, nightly]
rust_version: ['1.64.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, }
@@ -33,7 +63,8 @@ jobs:
- { 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, cmd: 'apk --' }
- { 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, }
@@ -44,7 +75,6 @@ jobs:
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
PKG_CONFIG_ALLOW_CROSS: 1
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
@@ -53,7 +83,7 @@ jobs:
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1
@@ -67,44 +97,55 @@ jobs:
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install Linux dependencies
if: (matrix.platform.os == 'ubuntu-latest')
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libfreetype6-dev:i386 libfontconfig1-dev:i386
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 $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
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.64.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests
shell: bash
if: (
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Lint with clippy
shell: bash
if: (matrix.rust_version == '1.57.0') && !contains(matrix.platform.options, '--no-default-features')
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.64.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: (
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32'))
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES

2
.gitignore vendored
View File

@@ -7,4 +7,4 @@ rls/
*.ts
*.js
#*#
.DS_Store
.DS_Store

View File

@@ -8,6 +8,186 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased
# 0.29.0-beta.0
- On Web, allow event loops to be recreated with `spawn`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- On X11, fix `EventLoopWindowTarget::listen_device_events` effect being reversed.
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Overhaul keyboard input handling.
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
- Change `Event::Key` to contain a `RawKeyEvent`.
- Remove `Event::ReceivedCharacter`. In its place, you should use
`KeyEvent.text` in combination with `WindowEvent::Ime`.
- Replace `VirtualKeyCode` with the `Key` enum.
- Replace `ScanCode` with the `KeyCode` enum.
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
- Add `KeyCode` to refer to keys (roughly) by their physical location.
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
understand.
- Add `Key` to represent the keys after they've been interpreted by the
active (software) keyboard layout.
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
but can appear simultaneously in different spots on the same keyboard
layout.
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
of dead key sequences.
- Add `KeyEventExtModifierSupplement` to expose additional (and less
portable) interpretations of a given key-press.
- Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and
`KeyCode`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- On Orbital, fix `ModifiersChanged` not being sent.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon::Arrow` was removed.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix forward compatibility issues.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable.
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
- **Breaking:** Bump MSRV from `1.60` to `1.64`.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
- On Web: fix position of touch events to be relative to the canvas.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during
a transient activation.
- On Web, fix pointer button events not being processed when a buttons is already pressed.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- On Windows, port to `windows-sys` version 0.48.0.
- On Web, fix pen treated as mouse input.
- On Web, send mouse position on button release as well.
- On Web, fix touch input not gaining or loosing focus.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- On Web, prevent clicks on the canvas to select text.
- On Web, `EventLoopProxy` now implements `Send`.
- On Web, `Window` now implements `Send` and `Sync`.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- On Web, use the correct canvas size when calculating the new size during scale factor change,
instead of using the output bitmap size.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On macOS, fix crash when dropping `Window`.
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to
the canvas size will be reported through `WindowEvent::Resized`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and
`DeviceEvent::Key` support.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events
on Wayland, X11, Windows, macOS and Web.
# 0.28.6
- On macOS, fixed memory leak when getting monitor handle.
- On macOS, fix `Backspace` being emitted when clearing preedit with it.
# 0.28.5
- On macOS, fix `key_up` being ignored when `Ime` is disabled.
# 0.28.4
- On macOS, fix empty marked text blocking regular input.
- On macOS, fix potential panic when getting refresh rate.
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.
# 0.28.3
- Fix macOS memory leaks.
# 0.28.2
- Implement `HasRawDisplayHandle` for `EventLoop`.
- On macOS, set resize increments only for live resizes.
- On Wayland, fix rare crash on DPI change
- Web: Added support for `Window::theme`.
- On Wayland, fix rounding issues when doing resize.
- On macOS, fix wrong focused state on startup.
- On Windows, fix crash on setting taskbar when using Visual Studio debugger.
- On macOS, resize simple fullscreen windows on windowDidChangeScreen events.
# 0.28.1
- On Wayland, fix crash when dropping a window in multi-window setup.
# 0.28.0
- On macOS, fixed `Ime::Commit` persisting for all input after interacting with `Ime`.
- On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`.
- On Windows, fix window size for maximized, undecorated windows.
- On Windows and macOS, add `WindowBuilder::with_active`.
- Add `Window::is_minimized`.
- On X11, fix errors handled during `register_xlib_error_hook` invocation bleeding into winit.
- Add `Window::has_focus`.
- On Windows, fix `Window::set_minimized(false)` not working for windows minimized by `Win + D` hotkey.
- **Breaking:** On Web, touch input no longer fires `WindowEvent::Cursor*`, `WindowEvent::MouseInput`, or `DeviceEvent::MouseMotion` like other platforms, but instead it fires `WindowEvent::Touch`.
- **Breaking:** Removed platform specific `WindowBuilder::with_parent` API in favor of `WindowBuilder::with_parent_window`.
- On Windows, retain `WS_MAXIMIZE` window style when un-minimizing a maximized window.
- On Windows, fix left mouse button release event not being sent after `Window::drag_window`.
- On macOS, run most actions on the main thread, which is strictly more correct, but might make multithreaded applications block slightly more.
- On macOS, fix panic when getting current monitor without any monitor attached.
- On Windows and MacOS, add API to enable/disable window buttons (close, minimize, ...etc).
- On Windows, macOS, X11 and Wayland, add `Window::set_theme`.
- **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`.
- On Windows, revert window background to an empty brush to avoid white flashes when changing scaling.
- **Breaking:** Removed `Window::set_always_on_top` and related APIs in favor of `Window::set_window_level`.
- On Windows, MacOS and X11, add always on bottom APIs.
- On Windows, fix the value in `MouseButton::Other`.
- On macOS, add `WindowExtMacOS::is_document_edited` and `WindowExtMacOS::set_document_edited` APIs.
- **Breaking:** Removed `WindowBuilderExtIOS::with_root_view_class`; instead, you should use `[[view layer] addSublayer: ...]` to add an instance of the desired layer class (e.g. `CAEAGLLayer` or `CAMetalLayer`). See `vulkano-win` or `wgpu` for examples of this.
- On MacOS and Windows, add `Window::set_content_protected`.
- On MacOS, add `EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps`.
- On Windows, fix icons specified on `WindowBuilder` not taking effect for windows created after the first one.
- On Windows and macOS, add `Window::title` to query the current window title.
- On Windows, fix focusing menubar when pressing `Alt`.
- On MacOS, made `accepts_first_mouse` configurable.
- Migrated `WindowBuilderExtUnix::with_resize_increments` to `WindowBuilder`.
- Added `Window::resize_increments`/`Window::set_resize_increments` to update resize increments at runtime for X11/macOS.
- macOS/iOS: Use `objc2` instead of `objc` internally.
- **Breaking:** Bump MSRV from `1.57` to `1.60`.
- **Breaking:** Split the `platform::unix` module into `platform::x11` and `platform::wayland`. The extension types are similarly renamed.
- **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`.
- Removed `parking_lot` dependency.
- **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`.
- **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop.
- On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations.
- On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations.
- On Wayland, if not otherwise specified use upstream automatic CSD theme selection.
- On X11, added `WindowExtX11::with_parent` to create child windows.
- Added support for `WindowBuilder::with_theme` and `Window::theme` to support per-window dark/light/system theme configuration on macos, windows and wayland.
- On macOS, added support for `WindowEvent::ThemeChanged`.
- **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`.
- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`.
- Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels.
- **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444))
- **Breaking:** Removed support for `raw-window-handle` version `0.4`
- On Wayland, `RedrawRequested` not emitted during resize.
- Add a `set_wait_timeout` function to `ControlFlow` to allow waiting for a `Duration`.
- **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface.
- Added Orbital support for Redox OS
- On X11, added `drag_resize_window` method.
- Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS.
- On macOS, fix the mouse buttons other than left/right/middle being reported as middle.
- On Wayland, support fractional scaling via the wp-fractional-scale protocol.
- On web, fix removal of mouse event listeners from the global object upon window distruction.
- Add WindowAttributes getter to WindowBuilder to allow introspection of default values.
- Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only.
# 0.27.5
- On Wayland, fix byte offset in `Ime::Preedit` pointing to invalid bytes.
@@ -18,7 +198,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Windows, fixed focus event emission on minimize.
- On X11, fixed IME crashing during reload.
# 0.27.3 (2022-9-10)
# 0.27.3
- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window.
- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled.
@@ -114,6 +294,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
- On MacOS, fix deadlock when calling `set_maximized` from event loop.
# 0.26.1 (2022-01-05)
@@ -215,7 +396,7 @@ And please only add new entries to the top of this list, right below the `# Unre
# 0.23.0 (2020-10-02)
- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode.
- On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode.
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
- On Unix, X11 and Wayland are now optional features (enabled by default)
- On X11, fix deadlock when calling `set_fullscreen_inner`.

View File

@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.57.0.
- your patch builds with Winit's minimal supported rust version - Rust 1.64.
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
@@ -48,15 +48,25 @@ The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
## Making a new release
## Release process
If you believe a new release is warranted, you can make a pull-request with:
- An updated version number (remember to change the version everywhere it is used).
- A new section in the changelog (below the `# Unreleased` section).
Given that winit is a widely used library we should be able to make a patch
releases at any time we want without blocking the development of new features.
This gives contributors an opportunity to squeeze in an extra PR or two that they feel is valuable
enough to warrant blocking the release a little.
To achieve these goals, a new branch is created for every new release. Releases
and later patch releases are committed and tagged in this branch.
Once the PR is merged, a maintainer will create a new tag matching the version name (e.g. `v0.26.1`),
and a CI job will automatically release the new version. Remember that the release date in the
changelog must be kept in check with the actual release date.
The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release
3. In the branch, the version is bumped to `v0.2.0`
4. The new commit in the branch is tagged `v0.2.0`
5. The version is pushed to crates.io
6. A GitHub release is created for the `v0.2.0` tag
7. On master, the version is bumped to `0.2.0` and the CHANGELOG is updated
When doing a patch release the process is similar:
1. Initially the version of the latest release is `0.2.0`
2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 3-7 of the regular release example

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.27.5"
version = "0.29.0-beta.0"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -10,7 +10,7 @@ readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.57.0"
rust-version = "1.64.0"
[package.metadata.docs.rs]
features = ["serde"]
@@ -32,48 +32,59 @@ targets = [
# WebAssembly
"wasm32-unknown-unknown",
]
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "wayland-protocols", "sctk"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/title"]
x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = ["android-activity/native-activity"]
android-game-activity = ["android-activity/game-activity"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
[build-dependencies]
cfg_aliases = "0.1.1"
[dependencies]
instant = { version = "0.1", features = ["wasm-bindgen"] }
once_cell = "1.12"
bitflags = "2"
cursor-icon = "1.0.0"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
raw_window_handle_04 = { package = "raw-window-handle", version = "0.4.3" }
bitflags = "1"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0"
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "2.1.0", default_features = false }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
android-activity = "0.4.0"
ndk = "0.7.0"
ndk-glue = "0.7.0"
ndk-sys = "0.4.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc = "0.2.7"
core-foundation = "0.9.3"
objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks`
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.24"
core-foundation = "0.9"
core-graphics = "0.22"
core-graphics = "0.22.3"
dispatch = "0.2.0"
[target.'cfg(target_os = "windows")'.dependencies]
parking_lot = "0.12"
unicode-segmentation = "1.7.1"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.36"
version = "0.48"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
@@ -101,27 +112,33 @@ features = [
"Win32_UI_WindowsAndMessaging",
]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.4.1", optional = true }
mio = { version = "0.8", features = ["os-ext"], optional = true }
x11-dl = { version = "2.18.5", optional = true }
percent-encoding = { version = "2.0", optional = true }
parking_lot = { version = "0.12.0", optional = true }
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
libc = "0.2.64"
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"
x11-dl = { version = "2.18.5", optional = true }
xkbcommon-dl = "0.4.0"
memmap2 = { version = "0.5.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }
redox_syscall = "0.3"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.22"
version = "0.3.64"
features = [
'console',
"AddEventListenerOptions",
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
@@ -130,19 +147,28 @@ features = [
'HtmlElement',
'KeyboardEvent',
'MediaQueryList',
'MediaQueryListEvent',
'MouseEvent',
'Node',
'PageTransitionEvent',
'PointerEvent',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'Window',
'WheelEvent'
]
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
[target.'cfg(target_family = "wasm")'.dependencies]
atomic-waker = "1"
js-sys = "0.3.64"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-time = "0.2"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.2"
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
[workspace]
members = [

View File

@@ -1,13 +1,14 @@
# Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling, and can
be used to create both games and applications. It supports the main graphical platforms:
be used to create both games and applications. It supports the following main graphical platforms:
- Desktop
- Windows
- macOS
- Windows 7+ (10+ is tested regularly)
- macOS 10.7+ (10.14+ is tested regularly)
- Unix
- via X11
- via Wayland
- Redox OS, via Orbital
- Mobile
- iOS
- Android
@@ -129,6 +130,8 @@ If your PR makes notable changes to Winit's features, please update this section
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
@@ -136,6 +139,7 @@ If your PR makes notable changes to Winit's features, please update this section
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* `winit` has a minimum OS requirement of iOS 8
@@ -148,7 +152,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Home indicator visibility
* Status bar visibility
* Deferrring system gestures
* Support for custom `UIView` derived class
* Getting the device idiom
* Getting the preferred video mode
@@ -169,62 +172,63 @@ Legend:
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|Window resize increments |❌ | | |❌ |❌ |❌ |**N/A**|
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**|
|Video mode query |✔️ |✔️ |✔️ |✔️ | |✔️ |**N/A**|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ | |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ | |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ | |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |❌ |❌ |✔️ |❌ |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ |
|Keyboard Input ([#812]) | |❌ | |❌ |❌ | | |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
@@ -239,5 +243,5 @@ Changes in the API that have been agreed upon but aren't implemented across all
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804
[#812]: https://github.com/rust-windowing/winit/issues/812

110
README.md
View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.27.5"
winit = "0.29.0-beta.0"
```
## [Documentation](https://docs.rs/winit)
@@ -73,10 +73,6 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
Note that windows don't appear on Wayland until you draw/present to them.
`winit` doesn't do drawing, try the examples in [`glutin`] instead.
[`glutin`]: https://github.com/rust-windowing/glutin
#### WebAssembly
To run the web example: `cargo run-wasm --example web`
@@ -99,36 +95,95 @@ book].
#### Android
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.
The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
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
various life-cycle events and synchronizing with the main JVM thread.
`winit` compatibility table with `ndk-glue`:
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
glue crate (prior to `0.28` it used
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
| winit | ndk-glue |
| :---: | :------------------: |
| 0.24 | `ndk-glue = "0.2.0"` |
| 0.25 | `ndk-glue = "0.3.0"` |
| 0.26 | `ndk-glue = "0.5.0"` |
| 0.27 | `ndk-glue = "0.7.0"` |
The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your
application's main entrypoint. If Cargo resolves multiple versions they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
| 0.25 | `ndk-glue = "0.3"` |
| 0.24 | `ndk-glue = "0.2"` |
The recommended way to avoid a conflict with the glue version is to avoid explicitly
depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*`
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[[example]]
name = "request_redraw_threaded"
[lib]
name = "main"
crate-type = ["cdylib"]
```
And add this to the example file to add the native activity glue:
All Android applications are based on an `Activity` subclass and the
`android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag:
| Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
[agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For example, add this to Cargo.toml:
```toml
winit = { version = "0.28", features = [ "android-native-activity" ] }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11.0"
```
And, for example, define an entry point for your library like this:
```rust
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
fn main() {
...
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));
let event_loop = EventLoopBuilder::with_user_event()
.with_android_app(app)
.build();
_main(event_loop);
}
```
And run the application with `cargo apk run --example request_redraw_threaded`
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
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" ] }`
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).
#### MacOS
@@ -139,6 +194,19 @@ and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::NewEvents(StartCause::Init)`.
#### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI related code, see issue [#1705]. You should consider creating your windows
inside `Event::NewEvents(StartCause::Init)`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
[#2051]: https://github.com/rust-windowing/winit/issues/2051
[#2087]: https://github.com/rust-windowing/winit/issues/2087
[#1705]: https://github.com/rust-windowing/winit/issues/1705
#### Redox OS
Redox OS has some functionality not present yet, that will be implemented when
its orbital display server provides it.

24
build.rs Normal file
View File

@@ -0,0 +1,24 @@
use cfg_aliases::cfg_aliases;
fn main() {
// The script doesn't depend on our code
println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
wasm_platform: { target_family = "wasm" },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform)) },
redox: { target_os = "redox" },
// Native displays.
x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) },
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
orbital_platform: { redox },
}
}

7
clippy.toml Normal file
View File

@@ -0,0 +1,7 @@
disallowed-methods = [
{ path = "web_sys::window", reason = "is not available in every context" },
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
]

70
deny.toml Normal file
View File

@@ -0,0 +1,70 @@
# https://embarkstudios.github.io/cargo-deny/
# cargo install cargo-deny
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
targets = [
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown" },
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
{ triple = "x86_64-pc-windows-msvc" },
{ triple = "x86_64-unknown-linux-gnu" },
{ triple = "x86_64-unknown-redox" },
]
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
ignore = []
[bans]
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
]
skip-tree = []
[licenses]
private = { ignore = true }
unlicensed = "deny"
allow-osi-fsf-free = "neither"
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
copyleft = "deny"
allow = [
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
]

11
docs/res/ATTRIBUTION.md Normal file
View File

@@ -0,0 +1,11 @@
# Image Attribution
These images are used in the documentation of `winit`.
## keyboard_*.svg
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

88
examples/child_window.rs Normal file
View File

@@ -0,0 +1,88 @@
#[cfg(any(x11_platform, macos_platform, windows_platform))]
#[path = "util/fill.rs"]
mod fill;
#[cfg(any(x11_platform, macos_platform, windows_platform))]
fn main() {
use std::collections::HashMap;
use raw_window_handle::HasRawWindowHandle;
use winit::{
dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
window::{Window, WindowBuilder, WindowId},
};
fn spawn_child_window(
parent: &Window,
event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>,
) {
let parent = parent.raw_window_handle();
let mut builder = WindowBuilder::new()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
builder = unsafe { builder.with_parent_window(Some(parent)) };
let child_window = builder.build(event_loop).unwrap();
let id = child_window.id();
windows.insert(id, child_window);
println!("child window created with id: {id:?}");
}
let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new();
let parent_window = WindowBuilder::new()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32))
.build(&event_loop)
.unwrap();
println!("parent window: {parent_window:?})");
event_loop.run(move |event: Event<'_, ()>, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
windows.clear();
*control_flow = ControlFlow::Exit;
}
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created
// by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the parent window,
// so we also can see this log when we move the cursor arround (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
} => {
spawn_child_window(&parent_window, event_loop, &mut windows);
}
_ => (),
}
} 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)))]
fn main() {
panic!("This example is supported only on x11, macOS, and Windows.");
}

View File

@@ -1,14 +1,22 @@
#![allow(clippy::single_match)]
use std::{thread, time};
use std::thread;
#[cfg(not(wasm_platform))]
use std::time;
#[cfg(wasm_platform)]
use web_time as time;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Wait,
@@ -40,8 +48,8 @@ fn main() {
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
use winit::event::{ElementState, StartCause, VirtualKeyCode};
println!("{:?}", event);
use winit::event::StartCause;
println!("{event:?}");
match event {
Event::NewEvents(start_cause) => {
wait_cancelled = match start_cause {
@@ -54,31 +62,33 @@ fn main() {
close_requested = true;
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match virtual_code {
VirtualKeyCode::Key1 => {
} => match key.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character("1") => {
mode = Mode::Wait;
println!("\nmode: {:?}\n", mode);
println!("\nmode: {mode:?}\n");
}
VirtualKeyCode::Key2 => {
Key::Character("2") => {
mode = Mode::WaitUntil;
println!("\nmode: {:?}\n", mode);
println!("\nmode: {mode:?}\n");
}
VirtualKeyCode::Key3 => {
Key::Character("3") => {
mode = Mode::Poll;
println!("\nmode: {:?}\n", mode);
println!("\nmode: {mode:?}\n");
}
VirtualKeyCode::R => {
Key::Character("r") => {
request_redraw = !request_redraw;
println!("\nrequest_redraw: {}\n", request_redraw);
println!("\nrequest_redraw: {request_redraw}\n");
}
VirtualKeyCode::Escape => {
Key::Escape => {
close_requested = true;
}
_ => (),
@@ -93,13 +103,15 @@ fn main() {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
Event::RedrawEventsCleared => {
match mode {
Mode::Wait => control_flow.set_wait(),
Mode::WaitUntil => {
if !wait_cancelled {
control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
control_flow.set_wait_until(time::Instant::now() + WAIT_TIME);
}
}
Mode::Poll => {

View File

@@ -2,11 +2,14 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
window::{CursorIcon, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -23,8 +26,8 @@ fn main() {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
@@ -46,6 +49,9 @@ fn main() {
} => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
@@ -54,8 +60,7 @@ fn main() {
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Crosshair,
CursorIcon::Hand,
CursorIcon::Arrow,
CursorIcon::Pointer,
CursorIcon::Move,
CursorIcon::Text,
CursorIcon::Wait,

View File

@@ -2,11 +2,15 @@
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
window::{CursorGrabMode, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -25,45 +29,48 @@ fn main() {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
virtual_keycode: Some(key),
..
},
..
} => {
use winit::event::VirtualKeyCode::*;
let result = match key {
Escape => {
Key::Escape => {
control_flow.set_exit();
Ok(())
}
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());
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(()),
};
if let Err(err) = result {
println!("error: {}", err);
println!("error: {err}");
}
}
WindowEvent::ModifiersChanged(m) => modifiers = m,
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {:?}", delta),
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
DeviceEvent::Button { button, state } => match state {
ElementState::Pressed => println!("mouse button {} pressed", button),
ElementState::Released => println!("mouse button {} released", button),
ElementState::Pressed => println!("mouse button {button} pressed"),
ElementState::Released => println!("mouse button {button} released"),
},
_ => (),
},
Event::RedrawRequested(_) => fill::fill_window(&window),
_ => (),
}
});

View File

@@ -1,6 +1,6 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(wasm_platform))]
fn main() {
use simple_logger::SimpleLogger;
use winit::{
@@ -9,6 +9,9 @@ fn main() {
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug, Clone, Copy)]
enum CustomEvent {
Timer,
@@ -17,7 +20,7 @@ fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let _window = WindowBuilder::new()
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
@@ -39,17 +42,20 @@ fn main() {
control_flow.set_wait();
match event {
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}
#[cfg(target_arch = "wasm32")]
#[cfg(wasm_platform)]
fn main() {
panic!("This example is not supported on web.");
}

View File

@@ -2,13 +2,15 @@
use simple_logger::SimpleLogger;
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::{Window, WindowBuilder, WindowId},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -45,20 +47,27 @@ fn main() {
name_windows(entered_id, switched, &window_1, &window_2)
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::X),
logical_key: Key::Character(c),
..
},
..
} => {
} if c == "x" => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
_ => (),
},
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

@@ -1,10 +1,17 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::Key;
use winit::window::{Fullscreen, WindowBuilder};
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowExtMacOS;
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -26,12 +33,14 @@ fn main() {
let mut mode_index = 0;
let mut mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {}", mode);
println!("Mode: {mode}");
println!("Keys:");
println!("- Esc\tExit");
println!("- F\tToggle exclusive fullscreen mode");
println!("- B\tToggle borderless mode");
#[cfg(target_os = "macos")]
println!("- C\tToggle simple fullscreen mode");
println!("- S\tNext screen");
println!("- M\tNext mode for this screen");
println!("- D\tToggle window decorations");
@@ -45,68 +54,81 @@ fn main() {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match virtual_code {
VirtualKeyCode::Escape => control_flow.set_exit(),
VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => {
window.set_fullscreen(None);
}
VirtualKeyCode::F => {
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
println!("Setting mode: {:?}", fullscreen);
window.set_fullscreen(fullscreen);
}
VirtualKeyCode::B => {
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
println!("Setting mode: {:?}", fullscreen);
window.set_fullscreen(fullscreen);
}
VirtualKeyCode::S => {
monitor_index += 1;
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
monitor = mon;
} else {
monitor_index = 0;
monitor = elwt.available_monitors().next().expect("no monitor found!");
} => match key {
Key::Escape => control_flow.set_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() {
"f" | "b" if window.fullscreen().is_some() => {
window.set_fullscreen(None);
}
println!("Monitor: {:?}", monitor.name());
"f" => {
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
println!("Setting mode: {fullscreen:?}");
window.set_fullscreen(fullscreen);
}
"b" => {
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
println!("Setting mode: {fullscreen:?}");
window.set_fullscreen(fullscreen);
}
#[cfg(target_os = "macos")]
"c" => {
window.set_simple_fullscreen(!window.simple_fullscreen());
}
"s" => {
monitor_index += 1;
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
monitor = mon;
} else {
monitor_index = 0;
monitor =
elwt.available_monitors().next().expect("no monitor found!");
}
println!("Monitor: {:?}", monitor.name());
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {}", mode);
}
VirtualKeyCode::M => {
mode_index += 1;
if let Some(m) = monitor.video_modes().nth(mode_index) {
mode = m;
} else {
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {mode}");
}
println!("Mode: {}", mode);
}
VirtualKeyCode::D => {
decorations = !decorations;
window.set_decorations(decorations);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
VirtualKeyCode::Z => {
minimized = !minimized;
window.set_minimized(minimized);
}
"m" => {
mode_index += 1;
if let Some(m) = monitor.video_modes().nth(mode_index) {
mode = m;
} else {
mode_index = 0;
mode = monitor.video_modes().next().expect("no mode found");
}
println!("Mode: {mode}");
}
"d" => {
decorations = !decorations;
window.set_decorations(decorations);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
"z" => {
minimized = !minimized;
window.set_minimized(minimized);
}
_ => (),
},
_ => (),
},
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => {}
}
});

View File

@@ -2,16 +2,20 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
let window = WindowBuilder::new()
.with_title("Your faithful window")
.build(&event_loop)
.unwrap();
@@ -19,10 +23,6 @@ fn main() {
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
use winit::event::{
ElementState::Released,
VirtualKeyCode::{N, Y},
};
control_flow.set_wait();
match event {
@@ -46,16 +46,18 @@ fn main() {
// the Y key.
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state: Released,
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
match virtual_code {
Y => {
// 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!");
@@ -68,7 +70,7 @@ fn main() {
control_flow.set_exit();
}
}
N => {
Key::Character("n") => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
@@ -80,6 +82,9 @@ fn main() {
_ => (),
}
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -3,12 +3,16 @@
use log::LevelFilter;
use simple_logger::SimpleLogger;
use winit::{
dpi::PhysicalPosition,
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, Ime, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
keyboard::{Key, KeyCode},
window::{ImePurpose, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new()
.with_level(LevelFilter::Trace)
@@ -18,6 +22,7 @@ fn main() {
println!("IME position will system default");
println!("Click to set IME position to cursor's");
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
println!("Press F3 to cycle through IME purposes.");
let event_loop = EventLoop::new();
@@ -26,6 +31,7 @@ fn main() {
.build(&event_loop)
.unwrap();
let mut ime_purpose = ImePurpose::Normal;
let mut ime_allowed = true;
window.set_ime_allowed(ime_allowed);
@@ -60,38 +66,42 @@ fn main() {
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_position(ime_pos);
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
Event::WindowEvent {
event: WindowEvent::Ime(event),
..
} => {
println!("{:?}", event);
println!("{event:?}");
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_position(ime_pos);
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
Event::WindowEvent {
event: WindowEvent::ReceivedCharacter(ch),
event: WindowEvent::KeyboardInput { event, .. },
..
} => {
println!("ch: {:?}", ch);
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => {
println!("key: {:?}", input);
println!("key: {event:?}");
if input.state == ElementState::Pressed
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
{
if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 {
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME: {}\n", 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");
}
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}

65
examples/key_binding.rs Normal file
View File

@@ -0,0 +1,65 @@
#![allow(clippy::single_match)]
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, ModifiersState},
// WARNING: This is not available on all platforms (for example on the web).
platform::modifier_supplement::KeyEventExtModifierSupplement,
window::WindowBuilder,
};
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
fn main() {
println!("This example is not supported on this platform");
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
fn main() {
#[path = "util/fill.rs"]
mod fill;
simple_logger::SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(400.0, 200.0))
.build(&event_loop)
.unwrap();
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,
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
WindowEvent::KeyboardInput { event, .. } => {
if event.state == ElementState::Pressed && !event.repeat {
match event.key_without_modifiers().as_ref() {
Key::Character("1") => {
if modifiers.shift_key() {
println!("Shift + 1 | logical_key: {:?}", event.logical_key);
} else {
println!("1");
}
}
_ => (),
}
}
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
});
}

View File

@@ -1,6 +1,8 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::monitor::MonitorHandle;
use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
@@ -8,6 +10,49 @@ fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
dbg!(window.available_monitors().collect::<Vec<_>>());
dbg!(window.primary_monitor());
if let Some(mon) = window.primary_monitor() {
print_info("Primary output", mon);
}
for mon in window.available_monitors() {
if Some(&mon) == window.primary_monitor().as_ref() {
continue;
}
println!();
print_info("Output", mon);
}
}
fn print_info(intro: &str, monitor: MonitorHandle) {
if let Some(name) = monitor.name() {
println!("{intro}: {name}");
} else {
println!("{intro}: [no name]");
}
let PhysicalSize { width, height } = monitor.size();
print!(" Current mode: {width}x{height}");
if let Some(m_hz) = monitor.refresh_rate_millihertz() {
println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
} else {
println!();
}
let PhysicalPosition { x, y } = monitor.position();
println!(" Position: {x},{y}");
println!(" Scale factor: {}", monitor.scale_factor());
println!(" Available modes (width x height x bit-depth):");
for mode in monitor.video_modes() {
let PhysicalSize { width, height } = mode.size();
let bits = mode.bit_depth();
let m_hz = mode.refresh_rate_millihertz();
println!(
" {width}x{height}x{bits} @ {}.{} Hz",
m_hz / 1000,
m_hz % 1000
);
}
}

View File

@@ -7,6 +7,9 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -39,7 +42,7 @@ In other words, the deltas indicate the direction in which to move the content (
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
println!("mouse wheel Line Delta: ({},{})", x, y);
println!("mouse wheel Line Delta: ({x},{y})");
let pixels_per_line = 120.0;
let mut pos = window.outer_position().unwrap();
pos.x += (x * pixels_per_line) as i32;
@@ -56,6 +59,9 @@ In other words, the deltas indicate the direction in which to move the content (
},
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -1,15 +1,16 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(wasm_platform))]
fn main() {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
keyboard::{Key, ModifiersState},
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
};
const WINDOW_COUNT: usize = 3;
@@ -29,6 +30,7 @@ fn main() {
let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx);
let mut modifiers = ModifiersState::default();
thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
@@ -51,105 +53,116 @@ fn main() {
);
}
}
#[allow(deprecated)]
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
logical_key: key,
..
},
..
} => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift();
use VirtualKeyCode::*;
use Key::{ArrowLeft, ArrowRight};
window.set_title(&format!("{key:?}"));
let state = !modifiers.shift_key();
match key {
A => window.set_always_on_top(state),
C => window.set_cursor_icon(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),
D => window.set_decorations(!state),
// Cycle through video modes
Right | Left => {
Key::ArrowRight | Key::ArrowLeft => {
video_mode_id = match key {
Left => video_mode_id.saturating_sub(1),
Right => (video_modes.len() - 1).min(video_mode_id + 1),
ArrowLeft => video_mode_id.saturating_sub(1),
ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!("Picking video mode: {}", video_modes[video_mode_id]);
}
F => window.set_fullscreen(match (state, modifiers.alt()) {
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => {
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
// 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() {
"1" => window.set_window_level(WindowLevel::AlwaysOnTop),
"2" => window.set_window_level(WindowLevel::AlwaysOnBottom),
"3" => window.set_window_level(WindowLevel::Normal),
"c" => window.set_cursor_icon(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),
"d" => window.set_decorations(!state),
"f" => window.set_fullscreen(match (state, modifiers.alt_key()) {
(true, false) => Some(Fullscreen::Borderless(None)),
(true, true) => Some(Fullscreen::Exclusive(
video_modes[video_mode_id].clone(),
)),
(false, _) => None,
}),
"l" if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked)
{
println!("error: {err}");
}
}
(false, _) => None,
}),
L if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
println!("error: {}", err);
"g" if state => {
if let Err(err) =
window.set_cursor_grab(CursorGrabMode::Confined)
{
println!("error: {err}");
}
}
}
G if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
println!("error: {}", err);
"g" | "l" if !state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
println!("error: {err}");
}
}
}
G | L if !state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
println!("error: {}", err);
"h" => window.set_cursor_visible(!state),
"i" => {
println!("Info:");
println!("-> outer_position : {:?}", window.outer_position());
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
}
}
H => window.set_cursor_visible(!state),
I => {
println!("Info:");
println!("-> outer_position : {:?}", window.outer_position());
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
}
L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
false => None,
}),
M => window.set_maximized(state),
P => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * sign;
position
}),
Q => window.request_redraw(),
R => window.set_resizable(state),
S => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
W => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.set_cursor_position(Position::Physical(
PhysicalPosition::new(
size.width as i32 / 2,
size.height as i32 / 2,
),
))
.unwrap()
"l" => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE),
false => None,
}),
"m" => window.set_maximized(state),
"p" => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1 } else { -1 };
position.x += 10 * sign;
position.y += 10 * sign;
position
}),
"q" => window.request_redraw(),
"r" => window.set_resizable(state),
"s" => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
"w" => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
.set_cursor_position(Position::Physical(
PhysicalPosition::new(
size.width as i32 / 2,
size.height as i32 / 2,
),
))
.unwrap()
}
}
}
Z => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
window.set_visible(true);
}
"z" => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
window.set_visible(true);
}
_ => (),
},
_ => (),
}
}
@@ -168,10 +181,10 @@ fn main() {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape),
logical_key: Key::Escape,
..
},
..
@@ -191,7 +204,7 @@ fn main() {
})
}
#[cfg(target_arch = "wasm32")]
#[cfg(wasm_platform)]
fn main() {
panic!("Example not supported on Wasm");
}

View File

@@ -4,11 +4,15 @@ use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -29,7 +33,7 @@ fn main() {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {:?} has received the signal to close", window_id);
println!("Window {window_id:?} has received the signal to close");
// This drops the window, causing it to close.
windows.remove(&window_id);
@@ -39,15 +43,15 @@ fn main() {
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::N),
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);
@@ -55,6 +59,11 @@ fn main() {
_ => (),
}
}
Event::RedrawRequested(window_id) => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
})

View File

@@ -7,6 +7,9 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -17,7 +20,7 @@ fn main() {
.unwrap();
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
println!("{event:?}");
control_flow.set_wait();
@@ -34,6 +37,7 @@ fn main() {
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}

View File

@@ -1,8 +1,8 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(wasm_platform))]
fn main() {
use std::{thread, time};
use std::{sync::Arc, thread, time};
use simple_logger::SimpleLogger;
use winit::{
@@ -11,21 +11,30 @@ fn main() {
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let window = {
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
Arc::new(window)
};
thread::spawn(move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
thread::spawn({
let window = window.clone();
move || loop {
thread::sleep(time::Duration::from_secs(1));
window.request_redraw();
}
});
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
println!("{event:?}");
control_flow.set_wait();
@@ -36,13 +45,14 @@ fn main() {
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
});
}
#[cfg(target_arch = "wasm32")]
#[cfg(wasm_platform)]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

View File

@@ -3,11 +3,15 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::KeyCode,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -30,20 +34,23 @@ fn main() {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
event:
KeyEvent {
physical_key: KeyCode::Space,
state: ElementState::Released,
..
},
..
} => {
resizable = !resizable;
println!("Resizable: {}", resizable);
println!("Resizable: {resizable}");
window.set_resizable(resizable);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
});

79
examples/theme.rs Normal file
View File

@@ -0,0 +1,79 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::Key,
window::{Theme, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_theme(Some(Theme::Dark))
.build(&event_loop)
.unwrap();
println!("Initial theme: {:?}", window.theme());
println!("debugging keys:");
println!(" (A) Automatic theme");
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);
}
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));
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
});
}

View File

@@ -1,7 +1,10 @@
#![allow(clippy::single_match)]
use instant::Instant;
use std::time::Duration;
#[cfg(not(wasm_platform))]
use std::time::Instant;
#[cfg(wasm_platform)]
use web_time::Instant;
use simple_logger::SimpleLogger;
use winit::{
@@ -10,11 +13,14 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
@@ -22,7 +28,7 @@ fn main() {
let timer_length = Duration::new(1, 0);
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
println!("{event:?}");
match event {
Event::NewEvents(StartCause::Init) => {
@@ -36,6 +42,9 @@ fn main() {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -0,0 +1,51 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Touchpad gestures")
.build(&event_loop)
.unwrap();
println!("Only supported on macOS at the moment.");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::TouchpadMagnify { delta, .. } => {
if delta > 0.0 {
println!("Zoomed in {delta}");
} else {
println!("Zoomed out {delta}");
}
}
WindowEvent::SmartMagnify { .. } => {
println!("Smart zoom");
}
WindowEvent::TouchpadRotate { delta, .. } => {
if delta > 0.0 {
println!("Rotated counterclockwise {delta}");
} else {
println!("Rotated clockwise {delta}");
}
}
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
}

View File

@@ -7,6 +7,9 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -21,13 +24,16 @@ fn main() {
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
println!("{:?}", event);
println!("{event:?}");
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

86
examples/util/fill.rs Normal file
View File

@@ -0,0 +1,86 @@
//! Fill the window buffer with a solid color.
//!
//! Launching a window without drawing to it has unpredictable results varying from platform to
//! platform. In order to have well-defined examples, this module provides an easy way to
//! fill the window buffer with a solid color.
//!
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
use winit::window::Window;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub(super) fn fill_window(window: &Window) {
use softbuffer::{Context, Surface};
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use winit::window::WindowId;
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: Context,
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface>,
}
impl GraphicsContext {
fn new(w: &Window) -> Self {
Self {
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
surfaces: HashMap::new(),
}
}
fn surface(&mut self, w: &Window) -> &mut Surface {
self.surfaces.entry(w.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, w) }
.expect("Failed to create a softbuffer surface")
})
}
}
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = ManuallyDrop::new(RefCell::new(None));
}
GC.with(|gc| {
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc
.get_or_insert_with(|| GraphicsContext::new(window))
.surface(window);
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xFF181818;
let size = window.inner_size();
surface
.resize(
NonZeroU32::new(size.width).expect("Width must be greater than zero"),
NonZeroU32::new(size.height).expect("Height must be greater than zero"),
)
.expect("Failed to resize the softbuffer surface");
let mut buffer = surface
.buffer_mut()
.expect("Failed to get the softbuffer buffer");
buffer.fill(DARK_GRAY);
buffer
.present()
.expect("Failed to present the softbuffer buffer");
})
}
#[cfg(any(target_os = "android", target_os = "ios"))]
pub(super) fn fill_window(_window: &Window) {
// No-op on mobile platforms.
}

View File

@@ -17,6 +17,6 @@ fn main() {
println!("Listing available video modes:");
for mode in monitor.video_modes() {
println!("{}", mode);
println!("{mode}");
}
}

View File

@@ -1,9 +1,10 @@
#![allow(clippy::single_match)]
#![allow(clippy::disallowed_methods, clippy::single_match)]
use winit::{
event::{Event, WindowEvent},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
keyboard::KeyCode,
window::{Fullscreen, WindowBuilder},
};
pub fn main() {
@@ -14,13 +15,13 @@ pub fn main() {
.build(&event_loop)
.unwrap();
#[cfg(target_arch = "wasm32")]
let log_list = wasm::create_log_list(&window);
#[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();
#[cfg(target_arch = "wasm32")]
#[cfg(wasm_platform)]
wasm::log_event(&log_list, &event);
match event {
@@ -31,12 +32,31 @@ pub fn main() {
Event::MainEventsCleared => {
window.request_redraw();
}
Event::WindowEvent {
window_id,
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: KeyCode::KeyF,
state: ElementState::Released,
..
},
..
},
} if window_id == window.id() => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
_ => (),
}
});
}
#[cfg(target_arch = "wasm32")]
#[cfg(wasm_platform)]
mod wasm {
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
@@ -49,17 +69,20 @@ mod wasm {
super::main();
}
pub fn create_log_list(window: &Window) -> web_sys::Element {
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let canvas = window.canvas().unwrap();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
canvas.style().set_css_text("background-color: crimson;");
// Set a background color for the canvas to make it easier to tell where the canvas is for debugging purposes.
canvas
.style()
.set_property("background-color", "crimson")
.unwrap();
body.append_child(&canvas).unwrap();
let log_header = document.create_element("h2").unwrap();
@@ -81,7 +104,7 @@ mod wasm {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let log = document.create_element("li").unwrap();
log.set_text_content(Some(&format!("{:?}", event)));
log.set_text_content(Some(&format!("{event:?}")));
log_list
.insert_before(&log, log_list.first_child().as_ref())
.unwrap();

View File

@@ -0,0 +1,105 @@
#![allow(clippy::disallowed_methods)]
pub fn main() {
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
}
#[cfg(wasm_platform)]
mod wasm {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::HtmlCanvasElement;
use winit::{
dpi::PhysicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
};
const EXPLANATION: &str = "
This example draws a circle in the middle of a 4/1 aspect ratio canvas which acts as a useful demonstration of winit's resize handling on web.
Even when the browser window is resized or aspect-ratio of the canvas changed the circle should always:
* Fill the entire width or height of the canvas (whichever is smaller) without exceeding it.
* Be perfectly round
* Not be blurry or pixelated (there is no antialiasing so you may still see jagged edges depending on the DPI of your monitor)
Currently winit does not handle resizes on web so the circle is rendered incorrectly.
This example demonstrates the desired future functionality which will possibly be provided by https://github.com/rust-windowing/winit/pull/2074
";
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
// When running in a non-wasm environment this would set the window size to 100x100.
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
.with_inner_size(PhysicalSize::new(100, 100))
.build(&event_loop)
.unwrap();
let canvas = create_canvas(&window);
// Render once with the size info we currently have
render_circle(&canvas, window.inner_size());
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);
}
_ => (),
}
});
}
pub fn create_canvas(window: &Window) -> HtmlCanvasElement {
use winit::platform::web::WindowExtWebSys;
let web_window = web_sys::window().unwrap();
let document = web_window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
let canvas = window.canvas().unwrap();
canvas
.style()
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
body.append_child(&canvas).unwrap();
let explanation = document.create_element("pre").unwrap();
explanation.set_text_content(Some(EXPLANATION));
body.append_child(&explanation).unwrap();
canvas
}
pub fn render_circle(canvas: &HtmlCanvasElement, size: PhysicalSize<u32>) {
log::info!("rendering circle with canvas size: {:?}", size);
let context = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
context.begin_path();
context
.arc(
size.width as f64 / 2.0,
size.height as f64 / 2.0,
size.width.min(size.height) as f64 / 2.0,
0.0,
std::f64::consts::PI * 2.0,
)
.unwrap();
context.fill();
}
}

View File

@@ -7,6 +7,9 @@ use winit::{
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -19,7 +22,7 @@ fn main() {
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
println!("{:?}", event);
println!("{event:?}");
match event {
Event::WindowEvent {
@@ -29,6 +32,9 @@ fn main() {
Event::MainEventsCleared => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -0,0 +1,75 @@
#![allow(clippy::single_match)]
// This example is used by developers to test various window functions.
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::Key,
window::{WindowBuilder, WindowButtons},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(300.0, 300.0))
.build(&event_loop)
.unwrap();
eprintln!("Window Button keys:");
eprintln!(" (F) Toggle close button");
eprintln!(" (G) Toggle maximize button");
eprintln!(" (H) Toggle minimize button");
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::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}

View File

@@ -5,11 +5,15 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{DeviceEventFilter, EventLoop},
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::{Key, KeyCode},
window::{Fullscreen, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -32,28 +36,31 @@ fn main() {
let mut minimized = false;
let mut visible = true;
event_loop.set_device_event_filter(DeviceEventFilter::Never);
event_loop.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
// This used to use the virtual key, but the new API
// only provides the `physical_key` (`Code`).
Event::DeviceEvent {
event:
DeviceEvent::Key(KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
DeviceEvent::Key(RawKeyEvent {
physical_key,
state: ElementState::Released,
..
}),
..
} => match key {
VirtualKeyCode::M => {
} => match physical_key {
KeyCode::KeyM => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
window.focus_window();
}
}
VirtualKeyCode::V => {
KeyCode::KeyV => {
if !visible {
visible = !visible;
window.set_visible(visible);
@@ -64,17 +71,19 @@ fn main() {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
event:
KeyEvent {
logical_key: Key::Character(key_str),
state: ElementState::Pressed,
..
},
..
},
..
} => match key {
VirtualKeyCode::E => {
} => 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
}
@@ -89,7 +98,7 @@ fn main() {
eprintln!("no video modes available");
}
}
VirtualKeyCode::F => {
"f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
@@ -97,25 +106,25 @@ fn main() {
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
VirtualKeyCode::P => {
"p" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
VirtualKeyCode::M => {
"m" => {
minimized = !minimized;
window.set_minimized(minimized);
}
VirtualKeyCode::Q => {
"q" => {
control_flow.set_exit();
}
VirtualKeyCode::V => {
"v" => {
visible = !visible;
window.set_visible(visible);
}
VirtualKeyCode::X => {
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
@@ -125,6 +134,9 @@ fn main() {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});

View File

@@ -0,0 +1,146 @@
//! Demonstrates capability to create in-app draggable regions for client-side decoration support.
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::Key,
window::{CursorIcon, ResizeDirection, WindowBuilder},
};
const BORDER: f64 = 8.0;
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
.with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0))
.with_decorations(false)
.build(&event_loop)
.unwrap();
let mut border = false;
let mut cursor_location = None;
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Press 'B' to toggle borderless")
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CursorMoved { position, .. } => {
if !window.is_decorated() {
let new_location =
cursor_resize_direction(window.inner_size(), position, BORDER);
if new_location != cursor_location {
cursor_location = new_location;
window.set_cursor_icon(cursor_direction_icon(cursor_location))
}
}
}
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
if let Some(dir) = cursor_location {
let _res = window.drag_resize_window(dir);
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Released,
logical_key: Key::Character(c),
..
},
..
} if matches!(c.as_ref(), "B" | "b") => {
border = !border;
window.set_decorations(border);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
});
}
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {
match resize_direction {
Some(resize_direction) => match resize_direction {
ResizeDirection::East => CursorIcon::EResize,
ResizeDirection::North => CursorIcon::NResize,
ResizeDirection::NorthEast => CursorIcon::NeResize,
ResizeDirection::NorthWest => CursorIcon::NwResize,
ResizeDirection::South => CursorIcon::SResize,
ResizeDirection::SouthEast => CursorIcon::SeResize,
ResizeDirection::SouthWest => CursorIcon::SwResize,
ResizeDirection::West => CursorIcon::WResize,
},
None => CursorIcon::Default,
}
}
fn cursor_resize_direction(
win_size: winit::dpi::PhysicalSize<u32>,
position: winit::dpi::PhysicalPosition<f64>,
border_size: f64,
) -> Option<ResizeDirection> {
enum XDirection {
West,
East,
Default,
}
enum YDirection {
North,
South,
Default,
}
let xdir = if position.x < border_size {
XDirection::West
} else if position.x > (win_size.width as f64 - border_size) {
XDirection::East
} else {
XDirection::Default
};
let ydir = if position.y < border_size {
YDirection::North
} else if position.y > (win_size.height as f64 - border_size) {
YDirection::South
} else {
YDirection::Default
};
Some(match xdir {
XDirection::West => match ydir {
YDirection::North => ResizeDirection::NorthWest,
YDirection::South => ResizeDirection::SouthWest,
YDirection::Default => ResizeDirection::West,
},
XDirection::East => match ydir {
YDirection::North => ResizeDirection::NorthEast,
YDirection::South => ResizeDirection::SouthEast,
YDirection::Default => ResizeDirection::East,
},
XDirection::Default => match ydir {
YDirection::North => ResizeDirection::North,
YDirection::South => ResizeDirection::South,
YDirection::Default => return None,
},
})
}

View File

@@ -9,6 +9,9 @@ use winit::{
window::{Icon, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
@@ -42,6 +45,8 @@ fn main() {
}
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
}

View File

@@ -0,0 +1,75 @@
#![allow(clippy::single_match)]
#[cfg(target_os = "macos")]
use winit::platform::macos::{OptionAsAlt, WindowExtMacOS};
#[cfg(target_os = "macos")]
use winit::{
event::ElementState,
event::{Event, MouseButton, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
#[cfg(target_os = "macos")]
#[path = "util/fill.rs"]
mod fill;
/// Prints the keyboard events characters received when option_is_alt is true versus false.
/// A left mouse click will toggle option_is_alt.
#[cfg(target_os = "macos")]
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(&event_loop)
.unwrap();
window.set_ime_allowed(true);
let mut option_as_alt = window.option_as_alt();
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, .. } => 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::MainEventsCleared => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("This example is only supported on MacOS");
}

View File

@@ -0,0 +1,64 @@
use log::debug;
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(LogicalSize::new(128.0, 128.0))
.with_resize_increments(LogicalSize::new(25.0, 25.0))
.build(&event_loop)
.unwrap();
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:
WindowEvent::KeyboardInput {
event:
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() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
}

View File

@@ -2,14 +2,12 @@
// Limit this example to only compatible platforms.
#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "android",
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
android_platform,
orbital_platform,
))]
fn main() {
use std::{thread::sleep, time::Duration};
@@ -21,10 +19,14 @@ fn main() {
platform::run_return::EventLoopExtRunReturn,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
let mut event_loop = EventLoop::new();
SimpleLogger::new().init().unwrap();
let _window = WindowBuilder::new()
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
@@ -37,7 +39,7 @@ fn main() {
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{:?}", event);
println!("{event:?}");
}
match event {
@@ -50,6 +52,9 @@ fn main() {
Event::MainEventsCleared => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
@@ -60,7 +65,7 @@ fn main() {
}
}
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
#[cfg(any(ios_platform, wasm_platform))]
fn main() {
println!("This platform doesn't support run_return.");
}

View File

@@ -6,4 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cargo-run-wasm = "0.1.0"
cargo-run-wasm = "0.2.0"

View File

@@ -1,3 +1,3 @@
fn main() {
cargo_run_wasm::run_wasm();
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
}

View File

@@ -167,7 +167,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> {
pub x: P,
@@ -246,7 +246,7 @@ impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
}
/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
@@ -325,7 +325,7 @@ impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
}
/// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> {
pub width: P,
@@ -407,7 +407,7 @@ impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> {
pub width: P,
@@ -519,18 +519,8 @@ impl Size {
max.into().to_physical::<f64>(scale_factor),
);
let clamp = |input: f64, min: f64, max: f64| {
if input < min {
min
} else if input > max {
max
} else {
input
}
};
let width = clamp(input.width, min.width, max.width);
let height = clamp(input.height, min.height, max.height);
let width = input.width.clamp(min.width, max.width);
let height = input.height.clamp(min.height, max.height);
PhysicalSize::new(width, height).into()
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,14 @@
//! handle events.
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{error, fmt};
use instant::Instant;
use once_cell::sync::OnceCell;
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
#[cfg(not(wasm_platform))]
use std::time::{Duration, Instant};
#[cfg(wasm_platform)]
use web_time::{Duration, Instant};
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
@@ -66,6 +69,8 @@ impl EventLoopBuilder<()> {
}
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl<T> EventLoopBuilder<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
@@ -97,14 +102,24 @@ impl<T> EventLoopBuilder<T> {
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
/// will fall back on X11. If this variable is set with any other value, winit will panic.
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`.
///
/// [`platform`]: crate::platform
#[cfg_attr(
android,
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)]
#[cfg_attr(
not(android),
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> EventLoop<T> {
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
if EVENT_LOOP_CREATED.set(()).is_err() {
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
panic!("Creating EventLoop multiple times is not supported.");
}
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
EventLoop {
@@ -112,6 +127,11 @@ impl<T> EventLoopBuilder<T> {
_marker: PhantomData,
}
}
#[cfg(wasm_platform)]
pub(crate) fn allow_event_loop_recreation() {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
}
impl<T> fmt::Debug for EventLoop<T> {
@@ -145,16 +165,11 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
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.
///
/// ## Platform-specific
///
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
/// example when the scaling of the page has changed. This should be treated as an implementation
/// detail which should not be relied on.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
@@ -164,6 +179,7 @@ pub enum ControlFlow {
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
/// Send a [`LoopDestroyed`] 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`.
@@ -211,6 +227,20 @@ impl ControlFlow {
*self = Self::WaitUntil(instant);
}
/// Sets this to wait 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) {
match Instant::now().checked_add(timeout) {
Some(instant) => self.set_wait_until(instant),
None => self.set_wait(),
}
}
/// Sets this to [`ExitWithCode`]`(code)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
@@ -286,6 +316,13 @@ impl<T> EventLoop<T> {
}
}
unsafe impl<T> 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()
}
}
impl<T> Deref for EventLoop<T> {
type Target = EventLoopWindowTarget<T>;
fn deref(&self) -> &EventLoopWindowTarget<T> {
@@ -297,6 +334,7 @@ impl<T> EventLoopWindowTarget<T> {
/// Returns the list of all the monitors available on the system.
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
#[allow(clippy::useless_conversion)] // false positive on some platforms
self.p
.available_monitors()
.into_iter()
@@ -312,30 +350,25 @@ impl<T> EventLoopWindowTarget<T> {
/// **Wayland:** Always returns `None`.
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.p.primary_monitor()
self.p
.primary_monitor()
.map(|inner| MonitorHandle { inner })
}
/// Change [`DeviceEvent`] filter mode.
/// Change if or when [`DeviceEvent`]s are captured.
///
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
/// this filter at runtime to explicitly capture them again.
/// this at runtime to explicitly capture them again.
///
/// ## Platform-specific
///
/// - **Wayland / macOS / iOS / Android / Web:** Unsupported.
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "windows"
))]
self.p.set_device_event_filter(_filter);
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
#[cfg(any(x11_platform, wasm_platform, wayland_platform, windows))]
self.p.listen_device_events(_allowed);
}
}
@@ -393,19 +426,14 @@ impl<T> fmt::Display for EventLoopClosed<T> {
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
/// Filter controlling the propagation of device events.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum DeviceEventFilter {
/// Always filter out device events.
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum DeviceEvents {
/// Report device events regardless of window focus.
Always,
/// Filter out device events while the window is not focused.
Unfocused,
/// Report all device events regardless of window focus.
/// Only capture device events while the window is focused.
#[default]
WhenFocused,
/// Never capture device events.
Never,
}
impl Default for DeviceEventFilter {
fn default() -> Self {
Self::Unfocused
}
}

View File

@@ -34,8 +34,7 @@ impl fmt::Display for BadIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
byte_count,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
),
BadIcon::DimensionsVsPixelCount {
width,
@@ -43,10 +42,9 @@ impl fmt::Display for BadIcon {
width_x_height,
pixel_count,
} => write!(f,
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
width, height, pixel_count, width_x_height,
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
),
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"),
}
}
}

1688
src/keyboard.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -135,6 +135,8 @@
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![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)]
#[allow(unused_imports)]
@@ -145,9 +147,6 @@ extern crate log;
extern crate serde;
#[macro_use]
extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
pub mod dpi;
#[macro_use]
@@ -155,6 +154,7 @@ pub mod error;
pub mod event;
pub mod event_loop;
mod icon;
pub mod keyboard;
pub mod monitor;
mod platform_impl;
pub mod window;

View File

@@ -32,12 +32,9 @@ impl PartialOrd for VideoMode {
impl Ord for VideoMode {
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
// to `u32` there
let size: (u32, u32) = self.size().into();
let other_size: (u32, u32) = other.size().into();
self.monitor().cmp(&other.monitor()).then(
size.cmp(&other_size)
self.size()
.cmp(&other.size())
.then(
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
@@ -61,7 +58,7 @@ impl VideoMode {
///
/// ## Platform-specific
///
/// - **Wayland:** Always returns 32.
/// - **Wayland / Orbital:** Always returns 32.
/// - **iOS:** Always returns 32.
#[inline]
pub fn bit_depth(&self) -> u16 {
@@ -78,7 +75,9 @@ impl VideoMode {
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
self.video_mode.monitor()
MonitorHandle {
inner: self.video_mode.monitor(),
}
}
}
@@ -141,6 +140,9 @@ impl MonitorHandle {
/// The monitor refresh rate used by the system.
///
/// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor
/// the window is on is removed.
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// enter fullscreen should be used instead.
#[inline]
@@ -169,6 +171,8 @@ impl MonitorHandle {
/// - **Web:** Always returns an empty iterator
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner.video_modes()
self.inner
.video_modes()
.map(|video_mode| VideoMode { video_mode })
}
}

View File

@@ -1,11 +1,9 @@
#![cfg(any(target_os = "android"))]
use crate::{
event_loop::{EventLoop, EventLoopWindowTarget},
event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget},
window::{Window, WindowBuilder},
};
use ndk::configuration::Configuration;
use ndk_glue::Rect;
use android_activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
@@ -19,7 +17,7 @@ pub trait EventLoopWindowTargetExtAndroid {}
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;
fn config(&self) -> Configuration;
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for Window {
@@ -27,7 +25,7 @@ impl WindowExtAndroid for Window {
self.window.content_rect()
}
fn config(&self) -> Configuration {
fn config(&self) -> ConfigurationRef {
self.window.config()
}
}
@@ -38,3 +36,53 @@ impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
pub trait WindowBuilderExtAndroid {}
impl WindowBuilderExtAndroid for WindowBuilder {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
///
/// Default is to let the operating system handle the volume keys
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
/// applications can rely on the Winit crate to resolve the required version of
/// `android_activity` and avoid any chance of a conflict between Winit and the
/// application crate.
///
/// Unlike most libraries there can only be a single implementation
/// of the `android_activity` glue crate linked with an application because
/// it is responsible for the application's `android_main()` entry point.
///
/// Since Winit depends on a specific version of `android_activity` the simplest
/// way to avoid creating a conflict is for applications to avoid explicitly
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os="android")]
/// use winit::platform::android::activity::AndroidApp;
/// ```
pub mod activity {
pub use android_activity::*;
}

View File

@@ -1,7 +1,7 @@
#![cfg(target_os = "ios")]
use std::os::raw::c_void;
use objc2::rc::Id;
use crate::{
event_loop::EventLoop,
monitor::{MonitorHandle, VideoMode},
@@ -99,17 +99,17 @@ pub trait WindowExtIOS {
impl WindowExtIOS for Window {
#[inline]
fn ui_window(&self) -> *mut c_void {
self.window.ui_window() as _
self.window.ui_window()
}
#[inline]
fn ui_view_controller(&self) -> *mut c_void {
self.window.ui_view_controller() as _
self.window.ui_view_controller()
}
#[inline]
fn ui_view(&self) -> *mut c_void {
self.window.ui_view() as _
self.window.ui_view()
}
#[inline]
@@ -141,13 +141,6 @@ impl WindowExtIOS for Window {
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
pub trait WindowBuilderExtIOS {
/// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
///
/// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc).
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
@@ -197,12 +190,6 @@ pub trait WindowBuilderExtIOS {
}
impl WindowBuilderExtIOS for WindowBuilder {
#[inline]
fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder {
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
self
}
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
self.platform_specific.scale_factor = Some(scale_factor);
@@ -254,19 +241,22 @@ pub trait MonitorHandleExtIOS {
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
self.inner.ui_screen() as _
Id::as_ptr(self.inner.ui_screen()) as *mut c_void
}
#[inline]
fn preferred_video_mode(&self) -> VideoMode {
self.inner.preferred_video_mode()
VideoMode {
video_mode: self.inner.preferred_video_mode(),
}
}
}
/// Valid orientations for a particular [`Window`].
#[derive(Clone, Copy, Debug)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone
#[default]
LandscapeAndPortrait,
Landscape,
@@ -275,17 +265,10 @@ pub enum ValidOrientations {
Portrait,
}
impl Default for ValidOrientations {
#[inline]
fn default() -> ValidOrientations {
ValidOrientations::LandscapeAndPortrait
}
}
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Idiom {
Unspecified,
@@ -304,14 +287,14 @@ bitflags! {
/// The [edges] of a screen.
///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
#[derive(Default)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScreenEdge: u8 {
const NONE = 0;
const TOP = 1 << 0;
const LEFT = 1 << 1;
const BOTTOM = 1 << 2;
const RIGHT = 1 << 3;
const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits
| ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits;
const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits()
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
}
}

View File

@@ -1,9 +1,8 @@
#![cfg(target_os = "macos")]
use std::os::raw::c_void;
use objc2::rc::Id;
use crate::{
dpi::LogicalSize,
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
@@ -38,6 +37,36 @@ pub trait WindowExtMacOS {
/// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool);
/// Get the window's edit state.
///
/// # Examples
///
/// ```ignore
/// WindowEvent::CloseRequested => {
/// if window.is_document_edited() {
/// // Show the user a save pop-up or similar
/// } else {
/// // Close the window
/// drop(window);
/// }
/// }
/// ```
fn is_document_edited(&self) -> bool;
/// Put the window in a state which indicates a file save is required.
fn set_document_edited(&self, edited: bool);
/// Set option as alt behavior as described in [`OptionAsAlt`].
///
/// This will ignore diacritical marks and accent characters from
/// being processed as received characters. Instead, the input
/// device's raw character will be placed in event queues with the
/// Alt modifier set.
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt;
}
impl WindowExtMacOS for Window {
@@ -70,25 +99,42 @@ impl WindowExtMacOS for Window {
fn set_has_shadow(&self, has_shadow: bool) {
self.window.set_has_shadow(has_shadow)
}
#[inline]
fn is_document_edited(&self) -> bool {
self.window.is_document_edited()
}
#[inline]
fn set_document_edited(&self, edited: bool) {
self.window.set_document_edited(edited)
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window.set_option_as_alt(option_as_alt)
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
self.window.option_as_alt()
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`.
#[default]
Regular,
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
Accessory,
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
Prohibited,
}
impl Default for ActivationPolicy {
fn default() -> Self {
ActivationPolicy::Regular
}
}
/// Additional methods on [`WindowBuilder`] that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
@@ -111,10 +157,14 @@ pub trait WindowBuilderExtMacOS {
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
/// Build window with `resizeIncrements` property. Values must not be 0.
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
/// Window accepts click-through mouse events.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
/// 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;
}
impl WindowBuilderExtMacOS for WindowBuilder {
@@ -157,12 +207,6 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments);
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
self.platform_specific.disallow_hidpi = disallow_hidpi;
@@ -174,6 +218,18 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self.platform_specific.has_shadow = has_shadow;
self
}
#[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> WindowBuilder {
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
self.platform_specific.option_as_alt = option_as_alt;
self
}
}
pub trait EventLoopBuilderExtMacOS {
@@ -220,6 +276,12 @@ pub trait EventLoopBuilderExtMacOS {
/// # }
/// ```
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
/// Used to prevent the application from automatically activating when launched if
/// another application is already active.
///
/// The default behavior is to ignore other applications and activate when launched.
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
@@ -234,6 +296,12 @@ impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
self.platform_specific.default_menu = enable;
self
}
#[inline]
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
self.platform_specific.activate_ignoring_other_apps = ignore;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
@@ -251,7 +319,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
}
fn ns_screen(&self) -> Option<*mut c_void> {
self.inner.ns_screen().map(|s| s as *mut c_void)
self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
}
}
@@ -272,3 +340,23 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
self.p.hide_other_applications()
}
}
/// Option as alt behavior.
///
/// The default is `None`.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OptionAsAlt {
/// The left `Option` key is treated as `Alt`.
OnlyLeft,
/// The right `Option` key is treated as `Alt`.
OnlyRight,
/// Both `Option` keys are treated as `Alt`.
Both,
/// No special handling is applied for `Option` key.
#[default]
None,
}

View File

@@ -15,11 +15,31 @@
//!
//! However only the module corresponding to the platform you're compiling to will be available.
#[cfg(android_platform)]
pub mod android;
#[cfg(ios_platform)]
pub mod ios;
#[cfg(macos_platform)]
pub mod macos;
pub mod unix;
#[cfg(orbital_platform)]
pub mod orbital;
#[cfg(wayland_platform)]
pub mod wayland;
#[cfg(wasm_platform)]
pub mod web;
#[cfg(windows_platform)]
pub mod windows;
#[cfg(x11_platform)]
pub mod x11;
pub mod modifier_supplement;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
orbital_platform
))]
pub mod run_return;
pub mod scancode;

View File

@@ -0,0 +1,24 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::Key;
/// Additional methods for the `KeyEvent` which cannot be implemented on all
/// platforms.
pub trait KeyEventExtModifierSupplement {
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
fn text_with_all_modifiers(&self) -> Option<&str>;
/// This value ignores all modifiers including,
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
/// and <kbd>Ctrl</kbd>. In most cases this means that the
/// unicode character in the resulting string is lowercase.
///
/// This is useful for key-bindings / shortcut key combinations.
///
/// In case `logical_key` reports `Dead`, this will still report the
/// key as `Character` according to the current keyboard layout. This value
/// cannot be `Dead`.
fn key_without_modifiers(&self) -> Key;
}

1
src/platform/orbital.rs Normal file
View File

@@ -0,0 +1 @@
// There are no Orbital specific traits yet.

View File

@@ -1,14 +1,3 @@
#![cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},

30
src/platform/scancode.rs Normal file
View File

@@ -0,0 +1,30 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::KeyCode;
// TODO: Describe what this value contains for each platform
/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific
/// scancode.
///
/// [`KeyCode`]: crate::keyboard::KeyCode
pub trait KeyCodeExtScancode {
/// The raw value of the platform-specific physical key identifier.
///
/// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise.
///
/// ## Platform-specific
/// - **Windows:** A 16bit extended scancode
/// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8.
fn to_scancode(self) -> Option<u32>;
/// Constructs a `KeyCode` from a platform-specific physical key identifier.
///
/// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back
/// using `to_scancode` might not yield the original value.
///
/// ## Platform-specific
/// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract
/// `8` to get the value you wanted.
fn from_scancode(scancode: u32) -> KeyCode;
}

View File

@@ -1,464 +0,0 @@
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use std::os::raw;
#[cfg(feature = "x11")]
use std::{ptr, sync::Arc};
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
#[cfg(feature = "x11")]
use crate::dpi::Size;
#[cfg(feature = "x11")]
use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS};
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
// TODO: stupid hack so that glutin can do its work
#[doc(hidden)]
#[cfg(feature = "x11")]
pub use crate::platform_impl::x11;
#[cfg(feature = "x11")]
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
#[cfg(feature = "wayland")]
pub use crate::window::Theme;
/// The first argument in the provided hook will be the pointer to `XDisplay`
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
/// indicator whether the error was handled by the callback.
///
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
#[cfg(feature = "x11")]
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors comming from X11 when using xlib
/// in external crates, like glutin for GLX access. Trying to handle errors by speculating with
/// `XSetErrorHandler` is [`unsafe`].
///
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
#[inline]
#[cfg(feature = "x11")]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
XLIB_ERROR_HOOKS.lock().push(hook);
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Unix.
pub trait EventLoopWindowTargetExtUnix {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool;
/// True if the [`EventLoopWindowTarget`] uses X11.
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
#[inline]
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.p {
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.display().get_display_ptr() as *mut _)
}
#[cfg(feature = "x11")]
_ => None,
}
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
pub trait EventLoopBuilderExtUnix {
/// Force using X11.
#[cfg(feature = "x11")]
fn with_x11(&mut self) -> &mut Self;
/// Force using Wayland.
#[cfg(feature = "wayland")]
fn with_wayland(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtUnix for EventLoopBuilder<T> {
#[inline]
#[cfg(feature = "x11")]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::X);
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::Wayland);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to Unix.
pub trait WindowExtUnix {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void>;
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int>;
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void>;
/// Updates [`Theme`] of window decorations.
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, config: Theme);
/// Check if the window is ready for drawing
///
/// It is a remnant of a previous implementation detail for the
/// wayland backend, and is no longer relevant.
///
/// Always return `true`.
#[deprecated]
fn is_ready(&self) -> bool;
}
impl WindowExtUnix for Window {
#[inline]
#[cfg(feature = "x11")]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(feature = "wayland")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
#[cfg(feature = "x11")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
#[cfg(feature = "x11")]
_ => None,
}
}
#[inline]
#[cfg(feature = "wayland")]
fn wayland_set_csd_theme(&self, theme: Theme) {
#[allow(clippy::single_match)]
match self.window {
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
#[cfg(feature = "x11")]
_ => (),
}
}
#[inline]
fn is_ready(&self) -> bool {
true
}
}
/// Additional methods on [`WindowBuilder`] that are specific to Unix.
pub trait WindowBuilderExtUnix {
#[cfg(feature = "x11")]
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
#[cfg(feature = "x11")]
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with the given `general` and `instance` names.
///
/// On Wayland, the `general` name sets an application ID, which should match the `.desktop`
/// file destributed with your program. The `instance` is a `no-op`.
///
/// On X11, the `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(self, variant: String) -> Self;
/// Build window with certain decoration [`Theme`]
///
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
/// Possible values for env variable are: "dark" and light"
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(self, theme: Theme) -> Self;
/// Build window with resize increment hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
/// Build window with base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::unix::WindowBuilderExtUnix;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
}
impl WindowBuilderExtUnix for WindowBuilder {
#[inline]
#[cfg(feature = "x11")]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
self.platform_specific.gtk_theme_variant = Some(variant);
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_wayland_csd_theme(mut self, theme: Theme) -> Self {
self.platform_specific.csd_theme = Some(theme);
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Linux.
pub trait MonitorHandleExtUnix {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtUnix for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

146
src/platform/wayland.rs Normal file
View File

@@ -0,0 +1,146 @@
use std::os::raw;
use sctk::reexports::client::Proxy;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
pub use crate::window::Theme;
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland.
pub trait EventLoopWindowTargetExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
fn is_wayland(&self) -> bool;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.connection.display().id().as_ptr() as *mut _)
}
#[cfg(x11_platform)]
_ => None,
}
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
pub trait EventLoopBuilderExtWayland {
/// Force using Wayland.
fn with_wayland(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::Wayland);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to Wayland.
pub trait WindowExtWayland {
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtWayland for Window {
#[inline]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
}
/// Additional methods on [`WindowBuilder`] that are specific to Wayland.
pub trait WindowBuilderExtWayland {
/// Build window with the given name.
///
/// The `general` name sets an application ID, which should match the `.desktop`
/// file destributed with your program. The `instance` is a `no-op`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
}
impl WindowBuilderExtWayland for WindowBuilder {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Wayland.
pub trait MonitorHandleExtWayland {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtWayland for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -1,5 +1,3 @@
#![cfg(target_arch = "wasm32")]
//! The web target does not automatically insert the canvas element object into the web page, to
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
@@ -14,7 +12,8 @@ use crate::window::WindowBuilder;
use web_sys::HtmlCanvasElement;
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
/// Only returns the canvas if called from inside the window.
fn canvas(&self) -> Option<HtmlCanvasElement>;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
@@ -28,6 +27,9 @@ pub trait WindowBuilderExtWebSys {
///
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
/// the default behavior of scrolling the page.
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
fn with_prevent_default(self, prevent_default: bool) -> Self;
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
@@ -64,6 +66,11 @@ pub trait EventLoopExtWebSys {
///
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
/// satisfy its `!` return type.
///
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
/// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application.
fn spawn<F>(self, event_handler: F)
where
F: 'static

View File

@@ -1,14 +1,14 @@
#![cfg(target_os = "windows")]
use std::{ffi::c_void, path::Path};
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event::{DeviceId, KeyEvent},
event_loop::EventLoopBuilder,
keyboard::Key,
monitor::MonitorHandle,
platform_impl::{Parent, WinIcon},
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
platform::modifier_supplement::KeyEventExtModifierSupplement,
platform_impl::WinIcon,
window::{BadIcon, Icon, Window, WindowBuilder},
};
/// Window Handle type used by Win32 API
@@ -138,9 +138,6 @@ pub trait WindowExtWindows {
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Returns the current window theme.
fn theme(&self) -> Theme;
/// Whether to show or hide the window icon in the taskbar.
fn set_skip_taskbar(&self, skip: bool);
@@ -171,11 +168,6 @@ impl WindowExtWindows for Window {
self.window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn theme(&self) -> Theme {
self.window.theme()
}
#[inline]
fn set_skip_taskbar(&self, skip: bool) {
self.window.set_skip_taskbar(skip)
@@ -189,14 +181,8 @@ impl WindowExtWindows for Window {
/// Additional methods on `WindowBuilder` that are specific to Windows.
pub trait WindowBuilderExtWindows {
/// Sets a parent to the window to be created.
///
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
/// 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`.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box.
///
@@ -234,9 +220,6 @@ pub trait WindowBuilderExtWindows {
/// 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;
/// Forces a theme or uses the system settings if `None` was provided.
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
@@ -248,15 +231,9 @@ pub trait WindowBuilderExtWindows {
}
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Parent::ChildOf(parent);
self
}
#[inline]
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Parent::OwnedBy(parent);
self.platform_specific.owner = Some(parent);
self
}
@@ -284,12 +261,6 @@ impl WindowBuilderExtWindows for WindowBuilder {
self
}
#[inline]
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
self.platform_specific.preferred_theme = theme;
self
}
#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
self.platform_specific.skip_taskbar = skip;
@@ -375,3 +346,18 @@ impl IconExtWindows for Icon {
Ok(Icon { inner: win_icon })
}
}
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifers
.as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}

233
src/platform/x11.rs Normal file
View File

@@ -0,0 +1,233 @@
use std::os::raw;
use std::ptr;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::dpi::Size;
use crate::platform_impl::{
x11::ffi::XVisualInfo, ApplicationName, Backend, Window as LinuxWindow, XLIB_ERROR_HOOKS,
};
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// The first argument in the provided hook will be the pointer to `XDisplay`
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
/// indicator whether the error was handled by the callback.
///
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors comming from X11
/// when using xlib in external crates, like glutin for GLX access. Trying to
/// handle errors by speculating with `XSetErrorHandler` is [`unsafe`].
///
/// **Be aware that your hook is always invoked and returning `true` from it will
/// prevent `winit` from getting the error itself. It's wise to always return
/// `false` if you're not initiated the `Sync`.**
///
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
#[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to X11.
pub trait EventLoopWindowTargetExtX11 {
/// True if the [`EventLoopWindowTarget`] uses X11.
fn is_x11(&self) -> bool;
}
impl<T> EventLoopWindowTargetExtX11 for EventLoopWindowTarget<T> {
#[inline]
fn is_x11(&self) -> bool {
!self.p.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to X11.
pub trait EventLoopBuilderExtX11 {
/// Force using X11.
fn with_x11(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(Backend::X);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to X11.
pub trait WindowExtX11 {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xlib_display(&self) -> Option<*mut raw::c_void>;
fn xlib_screen_id(&self) -> Option<raw::c_int>;
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtX11 for Window {
#[inline]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(wayland_platform)]
_ => None,
}
}
}
/// Additional methods on [`WindowBuilder`] that are specific to X11.
pub trait WindowBuilderExtX11 {
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with the given `general` and `instance` names.
///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
/// Build window with base size hint. Only implemented on X11.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::WindowBuilder;
/// # use winit::platform::x11::WindowBuilderExtX11;
/// // Specify the size in logical dimensions like this:
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
}
impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
self
}
#[inline]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self
}
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
self
}
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self
}
}
/// Additional methods on `MonitorHandle` that are specific to X11.
pub trait MonitorHandleExtX11 {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtX11 for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -0,0 +1,521 @@
use android_activity::input::Keycode;
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
match keycode {
Keycode::A => KeyCode::KeyA,
Keycode::B => KeyCode::KeyB,
Keycode::C => KeyCode::KeyC,
Keycode::D => KeyCode::KeyD,
Keycode::E => KeyCode::KeyE,
Keycode::F => KeyCode::KeyF,
Keycode::G => KeyCode::KeyG,
Keycode::H => KeyCode::KeyH,
Keycode::I => KeyCode::KeyI,
Keycode::J => KeyCode::KeyJ,
Keycode::K => KeyCode::KeyK,
Keycode::L => KeyCode::KeyL,
Keycode::M => KeyCode::KeyM,
Keycode::N => KeyCode::KeyN,
Keycode::O => KeyCode::KeyO,
Keycode::P => KeyCode::KeyP,
Keycode::Q => KeyCode::KeyQ,
Keycode::R => KeyCode::KeyR,
Keycode::S => KeyCode::KeyS,
Keycode::T => KeyCode::KeyT,
Keycode::U => KeyCode::KeyU,
Keycode::V => KeyCode::KeyV,
Keycode::W => KeyCode::KeyW,
Keycode::X => KeyCode::KeyX,
Keycode::Y => KeyCode::KeyY,
Keycode::Z => KeyCode::KeyZ,
Keycode::Keycode0 => KeyCode::Digit0,
Keycode::Keycode1 => KeyCode::Digit1,
Keycode::Keycode2 => KeyCode::Digit2,
Keycode::Keycode3 => KeyCode::Digit3,
Keycode::Keycode4 => KeyCode::Digit4,
Keycode::Keycode5 => KeyCode::Digit5,
Keycode::Keycode6 => KeyCode::Digit6,
Keycode::Keycode7 => KeyCode::Digit7,
Keycode::Keycode8 => KeyCode::Digit8,
Keycode::Keycode9 => KeyCode::Digit9,
Keycode::Numpad0 => KeyCode::Numpad0,
Keycode::Numpad1 => KeyCode::Numpad1,
Keycode::Numpad2 => KeyCode::Numpad2,
Keycode::Numpad3 => KeyCode::Numpad3,
Keycode::Numpad4 => KeyCode::Numpad4,
Keycode::Numpad5 => KeyCode::Numpad5,
Keycode::Numpad6 => KeyCode::Numpad6,
Keycode::Numpad7 => KeyCode::Numpad7,
Keycode::Numpad8 => KeyCode::Numpad8,
Keycode::Numpad9 => KeyCode::Numpad9,
Keycode::NumpadAdd => KeyCode::NumpadAdd,
Keycode::NumpadSubtract => KeyCode::NumpadSubtract,
Keycode::NumpadMultiply => KeyCode::NumpadMultiply,
Keycode::NumpadDivide => KeyCode::NumpadDivide,
Keycode::NumpadEnter => KeyCode::NumpadEnter,
Keycode::NumpadEquals => KeyCode::NumpadEqual,
Keycode::NumpadComma => KeyCode::NumpadComma,
Keycode::NumpadDot => KeyCode::NumpadDecimal,
Keycode::NumLock => KeyCode::NumLock,
Keycode::DpadLeft => KeyCode::ArrowLeft,
Keycode::DpadRight => KeyCode::ArrowRight,
Keycode::DpadUp => KeyCode::ArrowUp,
Keycode::DpadDown => KeyCode::ArrowDown,
Keycode::F1 => KeyCode::F1,
Keycode::F2 => KeyCode::F2,
Keycode::F3 => KeyCode::F3,
Keycode::F4 => KeyCode::F4,
Keycode::F5 => KeyCode::F5,
Keycode::F6 => KeyCode::F6,
Keycode::F7 => KeyCode::F7,
Keycode::F8 => KeyCode::F8,
Keycode::F9 => KeyCode::F9,
Keycode::F10 => KeyCode::F10,
Keycode::F11 => KeyCode::F11,
Keycode::F12 => KeyCode::F12,
Keycode::Space => KeyCode::Space,
Keycode::Escape => KeyCode::Escape,
Keycode::Enter => KeyCode::Enter, // not on the Numpad
Keycode::Tab => KeyCode::Tab,
Keycode::PageUp => KeyCode::PageUp,
Keycode::PageDown => KeyCode::PageDown,
Keycode::MoveHome => KeyCode::Home,
Keycode::MoveEnd => KeyCode::End,
Keycode::Insert => KeyCode::Insert,
Keycode::Del => KeyCode::Backspace, // Backspace (above Enter)
Keycode::ForwardDel => KeyCode::Delete, // Delete (below Insert)
Keycode::Copy => KeyCode::Copy,
Keycode::Paste => KeyCode::Paste,
Keycode::Cut => KeyCode::Cut,
Keycode::VolumeUp => KeyCode::AudioVolumeUp,
Keycode::VolumeDown => KeyCode::AudioVolumeDown,
Keycode::VolumeMute => KeyCode::AudioVolumeMute,
//Keycode::Mute => None, // Microphone mute
Keycode::MediaPlayPause => KeyCode::MediaPlayPause,
Keycode::MediaStop => KeyCode::MediaStop,
Keycode::MediaNext => KeyCode::MediaTrackNext,
Keycode::MediaPrevious => KeyCode::MediaTrackPrevious,
Keycode::Plus => KeyCode::Equal,
Keycode::Minus => KeyCode::Minus,
// Winit doesn't differentiate both '+' and '=', considering they are usually
// on the same physical key
Keycode::Equals => KeyCode::Equal,
Keycode::Semicolon => KeyCode::Semicolon,
Keycode::Slash => KeyCode::Slash,
Keycode::Backslash => KeyCode::Backslash,
Keycode::Comma => KeyCode::Comma,
Keycode::Period => KeyCode::Period,
Keycode::Apostrophe => KeyCode::Quote,
Keycode::Grave => KeyCode::Backquote,
// Winit doesn't expose a SysRq code, so map to PrintScreen since it's
// usually the same physical key
Keycode::Sysrq => KeyCode::PrintScreen,
// These are usually the same (Pause/Break)
Keycode::Break => KeyCode::Pause,
// These are exactly the same
Keycode::ScrollLock => KeyCode::ScrollLock,
Keycode::Yen => KeyCode::IntlYen,
Keycode::Kana => KeyCode::Lang1,
Keycode::KatakanaHiragana => KeyCode::KanaMode,
Keycode::CtrlLeft => KeyCode::ControlLeft,
Keycode::CtrlRight => KeyCode::ControlRight,
Keycode::ShiftLeft => KeyCode::ShiftLeft,
Keycode::ShiftRight => KeyCode::ShiftRight,
Keycode::AltLeft => KeyCode::AltLeft,
Keycode::AltRight => KeyCode::AltRight,
Keycode::MetaLeft => KeyCode::SuperLeft,
Keycode::MetaRight => KeyCode::SuperRight,
Keycode::LeftBracket => KeyCode::BracketLeft,
Keycode::RightBracket => KeyCode::BracketRight,
Keycode::Power => KeyCode::Power,
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp,
keycode => KeyCode::Unidentified(NativeKeyCode::Android(keycode.into())),
}
}
// TODO: We need to expose getUnicodeChar via android-activity instead of having
// a fixed mapping from key codes
pub fn to_logical(keycode: Keycode, native: NativeKey) -> Key {
use android_activity::input::Keycode::*;
match keycode {
Unknown => Key::Unidentified(native),
// Can be added on demand
SoftLeft => Key::Unidentified(native),
SoftRight => Key::Unidentified(native),
// Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::BrowserHome,
Back => Key::BrowserBack,
Call => Key::Call,
Endcall => Key::EndCall,
//-------------------------------------------------------------------------------
// Reporting unidentified, because the specific character is layout dependent.
// (I'm not sure though)
Keycode0 => Key::Character("0".into()),
Keycode1 => Key::Character("1".into()),
Keycode2 => Key::Character("2".into()),
Keycode3 => Key::Character("3".into()),
Keycode4 => Key::Character("4".into()),
Keycode5 => Key::Character("5".into()),
Keycode6 => Key::Character("6".into()),
Keycode7 => Key::Character("7".into()),
Keycode8 => Key::Character("8".into()),
Keycode9 => Key::Character("9".into()),
Star => Key::Character("*".into()),
Pound => Key::Character("#".into()),
A => Key::Character("a".into()),
B => Key::Character("b".into()),
C => Key::Character("c".into()),
D => Key::Character("d".into()),
E => Key::Character("e".into()),
F => Key::Character("f".into()),
G => Key::Character("g".into()),
H => Key::Character("h".into()),
I => Key::Character("i".into()),
J => Key::Character("j".into()),
K => Key::Character("k".into()),
L => Key::Character("l".into()),
M => Key::Character("m".into()),
N => Key::Character("n".into()),
O => Key::Character("o".into()),
P => Key::Character("p".into()),
Q => Key::Character("q".into()),
R => Key::Character("r".into()),
S => Key::Character("s".into()),
T => Key::Character("t".into()),
U => Key::Character("u".into()),
V => Key::Character("v".into()),
W => Key::Character("w".into()),
X => Key::Character("x".into()),
Y => Key::Character("y".into()),
Z => Key::Character("z".into()),
Comma => Key::Character(",".into()),
Period => Key::Character(".".into()),
Grave => Key::Character("`".into()),
Minus => Key::Character("-".into()),
Equals => Key::Character("=".into()),
LeftBracket => Key::Character("[".into()),
RightBracket => Key::Character("]".into()),
Backslash => Key::Character("\\".into()),
Semicolon => Key::Character(";".into()),
Apostrophe => Key::Character("'".into()),
Slash => Key::Character("/".into()),
At => Key::Character("@".into()),
Plus => Key::Character("+".into()),
//-------------------------------------------------------------------------------
DpadUp => Key::ArrowUp,
DpadDown => Key::ArrowDown,
DpadLeft => Key::ArrowLeft,
DpadRight => Key::ArrowRight,
DpadCenter => Key::Enter,
VolumeUp => Key::AudioVolumeUp,
VolumeDown => Key::AudioVolumeDown,
Power => Key::Power,
Camera => Key::Camera,
Clear => Key::Clear,
AltLeft => Key::Alt,
AltRight => Key::Alt,
ShiftLeft => Key::Shift,
ShiftRight => Key::Shift,
Tab => Key::Tab,
Space => Key::Space,
Sym => Key::Symbol,
Explorer => Key::LaunchWebBrowser,
Envelope => Key::LaunchMail,
Enter => Key::Enter,
Del => Key::Backspace,
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => Key::Alt,
Headsethook => Key::HeadsetHook,
Focus => Key::CameraFocus,
Menu => Key::Unidentified(native),
Notification => Key::Notification,
Search => Key::BrowserSearch,
MediaPlayPause => Key::MediaPlayPause,
MediaStop => Key::MediaStop,
MediaNext => Key::MediaTrackNext,
MediaPrevious => Key::MediaTrackPrevious,
MediaRewind => Key::MediaRewind,
MediaFastForward => Key::MediaFastForward,
Mute => Key::MicrophoneVolumeMute,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
Pictsymbols => Key::Unidentified(native),
SwitchCharset => Key::Unidentified(native),
// -----------------------------------------------------------------
// Gamepad events should be exposed through a separate API, not
// keyboard events
ButtonA => Key::Unidentified(native),
ButtonB => Key::Unidentified(native),
ButtonC => Key::Unidentified(native),
ButtonX => Key::Unidentified(native),
ButtonY => Key::Unidentified(native),
ButtonZ => Key::Unidentified(native),
ButtonL1 => Key::Unidentified(native),
ButtonR1 => Key::Unidentified(native),
ButtonL2 => Key::Unidentified(native),
ButtonR2 => Key::Unidentified(native),
ButtonThumbl => Key::Unidentified(native),
ButtonThumbr => Key::Unidentified(native),
ButtonStart => Key::Unidentified(native),
ButtonSelect => Key::Unidentified(native),
ButtonMode => Key::Unidentified(native),
// -----------------------------------------------------------------
Escape => Key::Escape,
ForwardDel => Key::Delete,
CtrlLeft => Key::Control,
CtrlRight => Key::Control,
CapsLock => Key::CapsLock,
ScrollLock => Key::ScrollLock,
MetaLeft => Key::Super,
MetaRight => Key::Super,
Function => Key::Fn,
Sysrq => Key::PrintScreen,
Break => Key::Pause,
MoveHome => Key::Home,
MoveEnd => Key::End,
Insert => Key::Insert,
Forward => Key::BrowserForward,
MediaPlay => Key::MediaPlay,
MediaPause => Key::MediaPause,
MediaClose => Key::MediaClose,
MediaEject => Key::Eject,
MediaRecord => Key::MediaRecord,
F1 => Key::F1,
F2 => Key::F2,
F3 => Key::F3,
F4 => Key::F4,
F5 => Key::F5,
F6 => Key::F6,
F7 => Key::F7,
F8 => Key::F8,
F9 => Key::F9,
F10 => Key::F10,
F11 => Key::F11,
F12 => Key::F12,
NumLock => Key::NumLock,
Numpad0 => Key::Character("0".into()),
Numpad1 => Key::Character("1".into()),
Numpad2 => Key::Character("2".into()),
Numpad3 => Key::Character("3".into()),
Numpad4 => Key::Character("4".into()),
Numpad5 => Key::Character("5".into()),
Numpad6 => Key::Character("6".into()),
Numpad7 => Key::Character("7".into()),
Numpad8 => Key::Character("8".into()),
Numpad9 => Key::Character("9".into()),
NumpadDivide => Key::Character("/".into()),
NumpadMultiply => Key::Character("*".into()),
NumpadSubtract => Key::Character("-".into()),
NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Enter,
NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()),
VolumeMute => Key::AudioVolumeMute,
Info => Key::Info,
ChannelUp => Key::ChannelUp,
ChannelDown => Key::ChannelDown,
ZoomIn => Key::ZoomIn,
ZoomOut => Key::ZoomOut,
Tv => Key::TV,
Window => Key::Unidentified(native),
Guide => Key::Guide,
Dvr => Key::DVR,
Bookmark => Key::BrowserFavorites,
Captions => Key::ClosedCaptionToggle,
Settings => Key::Settings,
TvPower => Key::TVPower,
TvInput => Key::TVInput,
StbPower => Key::STBPower,
StbInput => Key::STBInput,
AvrPower => Key::AVRPower,
AvrInput => Key::AVRInput,
ProgRed => Key::ColorF0Red,
ProgGreen => Key::ColorF1Green,
ProgYellow => Key::ColorF2Yellow,
ProgBlue => Key::ColorF3Blue,
AppSwitch => Key::AppSwitch,
Button1 => Key::Unidentified(native),
Button2 => Key::Unidentified(native),
Button3 => Key::Unidentified(native),
Button4 => Key::Unidentified(native),
Button5 => Key::Unidentified(native),
Button6 => Key::Unidentified(native),
Button7 => Key::Unidentified(native),
Button8 => Key::Unidentified(native),
Button9 => Key::Unidentified(native),
Button10 => Key::Unidentified(native),
Button11 => Key::Unidentified(native),
Button12 => Key::Unidentified(native),
Button13 => Key::Unidentified(native),
Button14 => Key::Unidentified(native),
Button15 => Key::Unidentified(native),
Button16 => Key::Unidentified(native),
LanguageSwitch => Key::GroupNext,
MannerMode => Key::MannerMode,
Keycode3dMode => Key::TV3DMode,
Contacts => Key::LaunchContacts,
Calendar => Key::LaunchCalendar,
Music => Key::LaunchMusicPlayer,
Calculator => Key::LaunchApplication2,
ZenkakuHankaku => Key::ZenkakuHankaku,
Eisu => Key::Eisu,
Muhenkan => Key::NonConvert,
Henkan => Key::Convert,
KatakanaHiragana => Key::HiraganaKatakana,
Yen => Key::Unidentified(native),
Ro => Key::Unidentified(native),
Kana => Key::KanjiMode,
Assist => Key::Unidentified(native),
BrightnessDown => Key::BrightnessDown,
BrightnessUp => Key::BrightnessUp,
MediaAudioTrack => Key::MediaAudioTrack,
Sleep => Key::Standby,
Wakeup => Key::WakeUp,
Pairing => Key::Pairing,
MediaTopMenu => Key::MediaTopMenu,
Keycode11 => Key::Unidentified(native),
Keycode12 => Key::Unidentified(native),
LastChannel => Key::MediaLast,
TvDataService => Key::TVDataService,
VoiceAssist => Key::VoiceDial,
TvRadioService => Key::TVRadioService,
TvTeletext => Key::Teletext,
TvNumberEntry => Key::TVNumberEntry,
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
TvTerrestrialDigital => Key::TVTerrestrialDigital,
TvSatellite => Key::TVSatellite,
TvSatelliteBs => Key::TVSatelliteBS,
TvSatelliteCs => Key::TVSatelliteCS,
TvSatelliteService => Key::TVSatelliteToggle,
TvNetwork => Key::TVNetwork,
TvAntennaCable => Key::TVAntennaCable,
TvInputHdmi1 => Key::TVInputHDMI1,
TvInputHdmi2 => Key::TVInputHDMI2,
TvInputHdmi3 => Key::TVInputHDMI3,
TvInputHdmi4 => Key::TVInputHDMI4,
TvInputComposite1 => Key::TVInputComposite1,
TvInputComposite2 => Key::TVInputComposite2,
TvInputComponent1 => Key::TVInputComponent1,
TvInputComponent2 => Key::TVInputComponent2,
TvInputVga1 => Key::TVInputVGA1,
TvAudioDescription => Key::TVAudioDescription,
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
TvZoomMode => Key::ZoomToggle,
TvContentsMenu => Key::TVContentsMenu,
TvMediaContextMenu => Key::TVMediaContext,
TvTimerProgramming => Key::TVTimer,
Help => Key::Help,
NavigatePrevious => Key::NavigatePrevious,
NavigateNext => Key::NavigateNext,
NavigateIn => Key::NavigateIn,
NavigateOut => Key::NavigateOut,
StemPrimary => Key::Unidentified(native),
Stem1 => Key::Unidentified(native),
Stem2 => Key::Unidentified(native),
Stem3 => Key::Unidentified(native),
DpadUpLeft => Key::Unidentified(native),
DpadDownLeft => Key::Unidentified(native),
DpadUpRight => Key::Unidentified(native),
DpadDownRight => Key::Unidentified(native),
MediaSkipForward => Key::MediaSkipForward,
MediaSkipBackward => Key::MediaSkipBackward,
MediaStepForward => Key::MediaStepForward,
MediaStepBackward => Key::MediaStepBackward,
SoftSleep => Key::Unidentified(native),
Cut => Key::Cut,
Copy => Key::Copy,
Paste => Key::Paste,
SystemNavigationUp => Key::Unidentified(native),
SystemNavigationDown => Key::Unidentified(native),
SystemNavigationLeft => Key::Unidentified(native),
SystemNavigationRight => Key::Unidentified(native),
AllApps => Key::Unidentified(native),
Refresh => Key::BrowserRefresh,
ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native),
}
}
pub fn to_location(keycode: Keycode) -> KeyLocation {
use android_activity::input::Keycode::*;
match keycode {
AltLeft => KeyLocation::Left,
AltRight => KeyLocation::Right,
ShiftLeft => KeyLocation::Left,
ShiftRight => KeyLocation::Right,
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => KeyLocation::Left,
CtrlLeft => KeyLocation::Left,
CtrlRight => KeyLocation::Right,
MetaLeft => KeyLocation::Left,
MetaRight => KeyLocation::Right,
NumLock => KeyLocation::Numpad,
Numpad0 => KeyLocation::Numpad,
Numpad1 => KeyLocation::Numpad,
Numpad2 => KeyLocation::Numpad,
Numpad3 => KeyLocation::Numpad,
Numpad4 => KeyLocation::Numpad,
Numpad5 => KeyLocation::Numpad,
Numpad6 => KeyLocation::Numpad,
Numpad7 => KeyLocation::Numpad,
Numpad8 => KeyLocation::Numpad,
Numpad9 => KeyLocation::Numpad,
NumpadDivide => KeyLocation::Numpad,
NumpadMultiply => KeyLocation::Numpad,
NumpadSubtract => KeyLocation::Numpad,
NumpadAdd => KeyLocation::Numpad,
NumpadDot => KeyLocation::Numpad,
NumpadComma => KeyLocation::Numpad,
NumpadEnter => KeyLocation::Numpad,
NumpadEquals => KeyLocation::Numpad,
NumpadLeftParen => KeyLocation::Numpad,
NumpadRightParen => KeyLocation::Numpad,
_ => KeyLocation::Standard,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,21 +9,27 @@ use std::{
time::Instant,
};
use objc::runtime::{BOOL, YES};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::foundation::{CGRect, CGSize, NSInteger, NSProcessInfo};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{msg_send, sel};
use once_cell::sync::Lazy;
use super::uikit::UIView;
use super::view::WinitUIWindow;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
ffi::{
id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer,
CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger,
NSOperatingSystemVersion, NSUInteger,
},
ffi::NSOperatingSystemVersion,
},
window::WindowId as RootWindowId,
};
@@ -62,25 +68,25 @@ impl Event<'static, Never> {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<id>,
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<id>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
},
Launching {
queued_windows: Vec<id>,
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_events: Vec<EventWrapper>,
queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<id>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
},
ProcessingEvents {
event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<id>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<id>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
},
ProcessingRedraws {
event_handler: Box<dyn EventHandler>,
@@ -103,28 +109,6 @@ struct AppState {
waker: EventLoopWaker,
}
impl Drop for AppState {
fn drop(&mut self) {
match self.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
..
}
| &mut AppStateImpl::Launching {
ref mut queued_windows,
..
} => {
for &mut window in queued_windows {
unsafe {
let _: () = msg_send![window, release];
}
}
}
_ => {}
}
}
}
impl AppState {
// requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> {
@@ -220,7 +204,9 @@ impl AppState {
});
}
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<EventWrapper>) {
fn did_finish_launching_transition(
&mut self,
) -> (Vec<Id<WinitUIWindow, Shared>>, Vec<EventWrapper>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
@@ -377,7 +363,7 @@ impl AppState {
}
}
fn main_events_cleared_transition(&mut self) -> HashSet<id> {
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow, Shared>> {
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents {
event_handler,
@@ -470,20 +456,13 @@ impl AppState {
// requires main thread and window is a UIWindow
// retains window
pub unsafe fn set_key_window(window: id) {
bug_assert!(
{
let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
is_window == YES
},
"set_key_window called with an incorrect type"
);
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
let mut this = AppState::get_mut();
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
..
} => return queued_windows.push(msg_send![window, retain]),
} => return queued_windows.push(window.clone()),
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
@@ -495,19 +474,12 @@ pub unsafe fn set_key_window(window: id) {
}
}
drop(this);
msg_send![window, makeKeyAndVisible]
window.makeKeyAndVisible();
}
// requires main thread and window is a UIWindow
// retains window
pub unsafe fn queue_gl_or_metal_redraw(window: id) {
bug_assert!(
{
let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
is_window == YES
},
"set_key_window called with an incorrect type"
);
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
let mut this = AppState::get_mut();
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
@@ -535,8 +507,6 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
panic!("Attempt to create a `Window` after the app has terminated")
}
}
drop(this);
}
// requires main thread
@@ -563,30 +533,25 @@ pub unsafe fn did_finish_launching() {
drop(this);
for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance.
// ```
let screen: id = msg_send![window, screen];
let _: id = msg_send![screen, retain];
let _: () = msg_send![window, setScreen:0 as id];
let _: () = msg_send![window, setScreen: screen];
let _: () = msg_send![screen, release];
let controller: id = msg_send![window, rootViewController];
let _: () = msg_send![window, setRootViewController:ptr::null::<()>()];
let _: () = msg_send![window, setRootViewController: controller];
let _: () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
let _: () = msg_send![&window, setScreen: ptr::null::<Object>()];
window.setScreen(&screen);
let controller = window.rootViewController();
window.setRootViewController(None);
window.setRootViewController(controller.as_deref());
window.makeKeyAndVisible();
}
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
@@ -600,12 +565,7 @@ pub unsafe fn did_finish_launching() {
// 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
for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
let _: () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
window.makeKeyAndVisible();
}
}
@@ -623,12 +583,12 @@ pub unsafe fn handle_wakeup_transition() {
}
// requires main thread
pub unsafe fn handle_nonuser_event(event: EventWrapper) {
pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) {
handle_nonuser_events(std::iter::once(event))
}
// requires main thread
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
let mut this = AppState::get_mut();
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
@@ -806,9 +766,7 @@ pub unsafe fn handle_main_events_cleared() {
let mut redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| {
EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))
})
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
.collect();
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
@@ -841,13 +799,13 @@ fn handle_event_proxy(
EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window_id,
window,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window_id,
window,
),
}
}
@@ -857,39 +815,34 @@ fn handle_hidpi_proxy(
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window_id: id,
window: Id<WinitUIWindow, Shared>,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
let event = Event::WindowEvent {
window_id: RootWindowId(window_id.into()),
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
};
event_handler.handle_nonuser_event(event, &mut control_flow);
let (view, screen_frame) = get_view_and_screen_frame(window_id);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size;
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size);
let size = CGSize::new(logical_size.width, logical_size.height);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
unsafe {
let _: () = msg_send![view, setFrame: new_frame];
}
view.setFrame(new_frame);
}
fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) {
unsafe {
let view_controller: id = msg_send![window_id, rootViewController];
let view: id = msg_send![view_controller, view];
let bounds: CGRect = msg_send![window_id, bounds];
let screen: id = msg_send![window_id, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![window_id, convertRect:bounds toCoordinateSpace:screen_space];
(view, screen_frame)
}
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView, Shared>, CGRect) {
let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap();
let bounds = window.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space);
(view, screen_frame)
}
struct EventLoopWaker {
@@ -1018,9 +971,9 @@ impl NSOperatingSystemVersion {
pub fn os_capabilities() -> OSCapabilities {
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
let atleast_ios_8: BOOL = msg_send![
process_info,
let process_info = NSProcessInfo::process_info();
let atleast_ios_8: bool = msg_send![
&process_info,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
@@ -1030,11 +983,8 @@ pub fn os_capabilities() -> OSCapabilities {
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(
atleast_ios_8 == YES,
"`winit` requires iOS version 8 or greater"
);
msg_send![process_info, operatingSystemVersion]
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
msg_send![&process_info, operatingSystemVersion]
};
version.into()
});

View File

@@ -3,47 +3,44 @@ use std::{
ffi::c_void,
fmt::{self, Debug},
marker::PhantomData,
mem, ptr,
ptr,
sync::mpsc::{self, Receiver, Sender},
};
use objc::runtime::Object;
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::foundation::{MainThreadMarker, NSString};
use objc2::rc::{Id, Shared};
use objc2::ClassType;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle};
use crate::{
dpi::LogicalSize,
event::Event,
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
},
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::Idiom,
};
use crate::platform_impl::platform::{
app_state,
ffi::{
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, UIApplicationMain,
UIUserInterfaceIdiom,
},
monitor, view, MonitorHandle,
};
#[derive(Debug)]
pub enum EventWrapper {
pub(crate) enum EventWrapper {
StaticEvent(Event<'static, Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub enum EventProxy {
pub(crate) enum EventProxy {
DpiChangedProxy {
window_id: id,
window: Id<WinitUIWindow, Shared>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
@@ -56,15 +53,13 @@ pub struct EventLoopWindowTarget<T: 'static> {
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread
unsafe { monitor::uiscreens() }
monitor::uiscreens(MainThreadMarker::new().unwrap())
}
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
// guaranteed to be on main thread
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
@@ -81,16 +76,16 @@ pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
static mut SINGLETON_INIT: bool = false;
unsafe {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
assert!(
!SINGLETON_INIT,
"Only one `EventLoop` is supported on iOS. \
`EventLoopProxy` might be helpful"
);
SINGLETON_INIT = true;
view::create_delegate_class();
}
let (sender_to_clone, receiver) = mpsc::channel();
@@ -114,24 +109,26 @@ impl<T: 'static> EventLoop<T> {
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let application: *mut Object = msg_send![class!(UIApplication), sharedApplication];
assert_eq!(
application,
ptr::null_mut(),
let application = UIApplication::shared(MainThreadMarker::new().unwrap());
assert!(
application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
);
app_state::will_launch(Box::new(EventLoopHandler {
f: event_handler,
event_loop: self.window_target,
}));
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
UIApplicationMain(
0,
ptr::null(),
nil,
NSStringRust::alloc(nil).init_str("AppDelegate"),
None,
Some(&NSString::from_str("WinitApplicationDelegate")),
);
unreachable!()
}
@@ -149,8 +146,9 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
// guaranteed to be on main thread
unsafe { self::get_idiom() }
UIDevice::current(MainThreadMarker::new().unwrap())
.userInterfaceIdiom()
.into()
}
}
@@ -180,14 +178,23 @@ impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
// we want all the members of context to be zero/null, except one
let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = Some(event_loop_proxy_handler);
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
@@ -223,7 +230,6 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -275,7 +281,7 @@ fn setup_control_flow_observers() {
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::min_value(),
control_flow_begin_handler,
@@ -345,10 +351,3 @@ where
}
}
}
// must be called on main thread
pub unsafe fn get_idiom() -> Idiom {
let device: id = msg_send![class!(UIDevice), currentDevice];
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
raw_idiom.into()
}

View File

@@ -1,24 +1,11 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
use std::convert::TryInto;
use objc::{runtime::Object, Encode, Encoding};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSUInteger};
use crate::{
dpi::LogicalSize,
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
};
pub type id = *mut Object;
pub const nil: id = 0 as id;
#[cfg(target_pointer_width = "32")]
pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
pub type NSInteger = isize;
pub type NSUInteger = usize;
use crate::platform::ios::{Idiom, ScreenEdge};
#[repr(C)]
#[derive(Clone, Debug)]
@@ -28,91 +15,15 @@ pub struct NSOperatingSystemVersion {
pub patch: NSInteger,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
}
impl CGSize {
pub fn new(size: LogicalSize<f64>) -> CGSize {
CGSize {
width: size.width as _,
height: size.height as _,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
impl CGRect {
pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
CGRect { origin, size }
}
}
unsafe impl Encode for CGRect {
fn encode() -> Encoding {
unsafe {
if cfg!(target_pointer_width = "32") {
Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}")
} else if cfg!(target_pointer_width = "64") {
Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}")
} else {
unimplemented!()
}
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
unsafe impl Encode for NSOperatingSystemVersion {
const ENCODING: Encoding = Encoding::Struct(
"NSOperatingSystemVersion",
&[
NSInteger::ENCODING,
NSInteger::ENCODING,
NSInteger::ENCODING,
],
);
}
#[repr(transparent)]
@@ -120,9 +31,7 @@ pub struct UIEdgeInsets {
pub struct UIUserInterfaceIdiom(NSInteger);
unsafe impl Encode for UIUserInterfaceIdiom {
fn encode() -> Encoding {
NSInteger::encode()
}
const ENCODING: Encoding = NSInteger::ENCODING;
}
impl UIUserInterfaceIdiom {
@@ -157,65 +66,12 @@ impl From<UIUserInterfaceIdiom> for Idiom {
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct UIInterfaceOrientationMask(NSUInteger);
unsafe impl Encode for UIInterfaceOrientationMask {
fn encode() -> Encoding {
NSUInteger::encode()
}
}
impl UIInterfaceOrientationMask {
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
pub const Landscape: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
pub const AllButUpsideDown: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
pub const All: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
}
impl BitOr for UIInterfaceOrientationMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
UIInterfaceOrientationMask(self.0 | rhs.0)
}
}
impl UIInterfaceOrientationMask {
pub fn from_valid_orientations_idiom(
valid_orientations: ValidOrientations,
idiom: Idiom,
) -> UIInterfaceOrientationMask {
match (valid_orientations, idiom) {
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => {
UIInterfaceOrientationMask::AllButUpsideDown
}
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
(ValidOrientations::Portrait, _) => {
UIInterfaceOrientationMask::Portrait
| UIInterfaceOrientationMask::PortraitUpsideDown
}
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
unsafe impl Encode for UIRectEdge {
fn encode() -> Encoding {
NSUInteger::encode()
}
const ENCODING: Encoding = NSUInteger::ENCODING;
}
impl From<ScreenEdge> for UIRectEdge {
@@ -235,158 +91,3 @@ impl From<UIRectEdge> for ScreenEdge {
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
fn encode() -> Encoding {
NSInteger::encode()
}
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
}
#[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode;
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
principalClassName: id,
delegateClassName: id,
) -> c_int;
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef,
activities: CFOptionFlags,
repeats: Boolean,
order: CFIndex,
callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) -> CFRunLoopObserverRef;
pub fn CFRunLoopAddObserver(
rl: CFRunLoopRef,
observer: CFRunLoopObserverRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerCreate(
allocator: CFAllocatorRef,
fireDate: CFAbsoluteTime,
interval: CFTimeInterval,
flags: CFOptionFlags,
order: CFIndex,
callout: CFRunLoopTimerCallBack,
context: *mut CFRunLoopTimerContext,
) -> CFRunLoopTimerRef;
pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode);
pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime);
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
pub fn CFRunLoopSourceCreate(
allocator: CFAllocatorRef,
order: CFIndex,
context: *mut CFRunLoopSourceContext,
) -> CFRunLoopSourceRef;
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
pub fn CFRelease(cftype: *const c_void);
}
pub type Boolean = u8;
pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {}
pub type CFRunLoopRef = *mut CFRunLoop;
pub type CFRunLoopMode = CFStringRef;
pub enum CFRunLoopObserver {}
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
pub enum CFRunLoopTimer {}
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
pub enum CFRunLoopSource {}
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
pub enum CFString {}
pub type CFStringRef = *const CFString;
pub type CFHashCode = c_ulong;
pub type CFIndex = c_long;
pub type CFOptionFlags = c_ulong;
pub type CFRunLoopActivity = CFOptionFlags;
pub type CFAbsoluteTime = CFTimeInterval;
pub type CFTimeInterval = f64;
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
pub type CFRunLoopObserverCallBack =
extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void);
pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
pub enum CFRunLoopObserverContext {}
pub enum CFRunLoopTimerContext {}
#[repr(C)]
pub struct CFRunLoopSourceContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
pub release: Option<extern "C" fn(*const c_void)>,
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
pub perform: Option<extern "C" fn(*mut c_void)>,
}
// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of
// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy
// so please test if you change the name back to NSString.
pub trait NSStringRust: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSString), alloc]
}
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
unsafe fn stringByAppendingString_(self, other: id) -> id;
unsafe fn init_str(self, string: &str) -> Self;
unsafe fn UTF8String(self) -> *const c_char;
}
impl NSStringRust for id {
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
msg_send![self, initWithUTF8String: c_string]
}
unsafe fn stringByAppendingString_(self, other: id) -> id {
msg_send![self, stringByAppendingString: other]
}
unsafe fn init_str(self, string: &str) -> id {
let cstring = CString::new(string).unwrap();
self.initWithUTF8String_(cstring.as_ptr())
}
unsafe fn UTF8String(self) -> *const c_char {
msg_send![self, UTF8String]
}
}

View File

@@ -55,7 +55,7 @@
//!
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
#![cfg(target_os = "ios")]
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
@@ -63,8 +63,7 @@
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
let is_main_thread: ::objc::runtime::BOOL = msg_send!(class!(NSThread), isMainThread);
if is_main_thread == ::objc::runtime::NO {
if !::objc2::foundation::is_main_thread() {
panic!($($t)*);
}
};
@@ -74,6 +73,7 @@ mod app_state;
mod event_loop;
mod ffi;
mod monitor;
mod uikit;
mod view;
mod window;
@@ -87,17 +87,19 @@ pub(crate) use self::{
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
use self::uikit::UIScreen;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {
uiscreen: ffi::id,
uiscreen: *const UIScreen,
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
DeviceId {
uiscreen: std::ptr::null_mut(),
uiscreen: std::ptr::null(),
}
}
}
@@ -105,6 +107,9 @@ impl DeviceId {
unsafe impl Send for DeviceId {}
unsafe impl Sync for DeviceId {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}
#[derive(Debug)]
pub enum OsError {}

View File

@@ -1,74 +1,48 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::{BTreeSet, VecDeque},
fmt,
ops::{Deref, DerefMut},
};
use objc2::foundation::{MainThreadMarker, NSInteger};
use objc2::rc::{Id, Shared};
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{
app_state,
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
},
monitor::VideoMode as RootVideoMode,
platform_impl::platform::app_state,
};
#[derive(Debug, PartialEq, Eq, Hash)]
// TODO(madsmtm): Remove or refactor this
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode, Shared>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
#[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: NativeDisplayMode,
pub(crate) screen_mode: ScreenModeSendSync,
pub(crate) monitor: MonitorHandle,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub id);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.0, release];
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
let _: id = msg_send![self.0, retain];
}
NativeDisplayMode(self.0)
}
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate_millihertz: self.refresh_rate_millihertz,
screen_mode: self.screen_mode.clone(),
monitor: self.monitor.clone(),
}
}
}
impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
fn new(uiscreen: Id<UIScreen, Shared>, screen_mode: Id<UIScreenMode, Shared>) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
let size: CGSize = msg_send![screen_mode, size];
let screen_mode: id = msg_send![screen_mode, retain];
let screen_mode = NativeDisplayMode(screen_mode);
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,
monitor: MonitorHandle::retained_new(uiscreen),
screen_mode: ScreenModeSendSync(screen_mode),
monitor: MonitorHandle::new(uiscreen),
}
}
@@ -84,51 +58,46 @@ impl VideoMode {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner {
uiscreen: id,
uiscreen: Id<UIScreen, Shared>,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.uiscreen, release];
}
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MonitorHandle {
inner: Inner,
}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self))
}
}
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
unsafe {
assert_main_thread!(
"`MonitorHandle` methods can only be run on the main thread on iOS"
);
}
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 {
unsafe {
assert_main_thread!(
"`MonitorHandle` methods can only be run on the main thread on iOS"
);
}
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&mut self.inner
}
}
@@ -136,17 +105,9 @@ impl DerefMut for MonitorHandle {
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Clone for MonitorHandle {
fn clone(&self) -> MonitorHandle {
MonitorHandle::retained_new(self.uiscreen)
}
}
impl Drop for MonitorHandle {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
@@ -174,12 +135,9 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub fn retained_new(uiscreen: id) -> MonitorHandle {
unsafe {
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
let _: id = msg_send![uiscreen, retain];
}
MonitorHandle {
pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
Self {
inner: Inner { uiscreen },
}
}
@@ -187,69 +145,62 @@ impl MonitorHandle {
impl Inner {
pub fn name(&self) -> Option<String> {
unsafe {
let main = main_uiscreen();
if self.uiscreen == main.uiscreen {
Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
Some("Mirrored".to_string())
} else {
uiscreens()
.iter()
.position(|rhs| rhs.uiscreen == self.uiscreen)
.map(|idx| idx.to_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())
}
}
pub fn size(&self) -> PhysicalSize<u32> {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
let bounds = self.uiscreen.nativeBounds();
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
pub fn position(&self) -> PhysicalPosition<i32> {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
let bounds = self.uiscreen.nativeBounds();
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
pub fn scale_factor(&self) -> f64 {
unsafe {
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
scale as f64
}
self.uiscreen.nativeScale() as f64
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(self.uiscreen))
Some(refresh_rate_millihertz(&self.uiscreen))
}
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let mut modes = BTreeSet::new();
unsafe {
let available_modes: id = msg_send![self.uiscreen, availableModes];
let available_mode_count: NSUInteger = msg_send![available_modes, count];
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = self
.uiscreen
.availableModes()
.into_iter()
.map(|mode| {
let mode: *const UIScreenMode = mode;
let mode = unsafe { Id::retain(mode as *mut UIScreenMode).unwrap() };
for i in 0..available_mode_count {
let mode: id = msg_send![available_modes, objectAtIndex: i];
modes.insert(RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
});
}
}
RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
}
})
.collect();
modes.into_iter()
modes.into_iter().map(|mode| mode.video_mode)
}
}
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
let refresh_rate_millihertz: NSInteger = unsafe {
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
let refresh_rate_millihertz: NSInteger = {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
uiscreen.maximumFramesPerSecond()
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
@@ -271,43 +222,25 @@ fn refresh_rate_millihertz(uiscreen: id) -> u32 {
// MonitorHandleExtIOS
impl Inner {
pub fn ui_screen(&self) -> id {
self.uiscreen
pub(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
&self.uiscreen
}
pub fn preferred_video_mode(&self) -> RootVideoMode {
unsafe {
let mode: id = msg_send![self.uiscreen, preferredMode];
RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
}
}
pub fn preferred_video_mode(&self) -> VideoMode {
VideoMode::new(
self.uiscreen.clone(),
self.uiscreen.preferredMode().unwrap(),
)
}
}
// requires being run on main thread
pub unsafe fn main_uiscreen() -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
let screens: id = msg_send![class!(UIScreen), screens];
let count: NSUInteger = msg_send![screens, count];
let mut result = VecDeque::with_capacity(count as _);
let screens_enum: id = msg_send![screens, objectEnumerator];
loop {
let screen: id = msg_send![screens_enum, nextObject];
if screen == nil {
break result;
}
result.push_back(MonitorHandle::retained_new(screen));
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()
.map(|screen| {
let screen: *const UIScreen = screen;
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
MonitorHandle::new(screen)
})
.collect()
}

View File

@@ -0,0 +1,30 @@
use objc2::foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIWindow};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIApplication;
unsafe impl ClassType for UIApplication {
#[inherits(NSObject)]
type Super = UIResponder;
}
);
extern_methods!(
unsafe impl UIApplication {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self, Shared>> {
unsafe { msg_send_id![Self::class(), sharedApplication] }
}
pub fn windows(&self) -> Id<NSArray<UIWindow, Shared>, Shared> {
unsafe { msg_send_id![self, windows] }
}
#[sel(statusBarFrame)]
pub fn statusBarFrame(&self) -> CGRect;
}
);

View File

@@ -0,0 +1,11 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UICoordinateSpace;
unsafe impl ClassType for UICoordinateSpace {
type Super = NSObject;
}
);

View File

@@ -0,0 +1,25 @@
use objc2::foundation::{MainThreadMarker, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::super::ffi::UIUserInterfaceIdiom;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIDevice;
unsafe impl ClassType for UIDevice {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), currentDevice] }
}
#[sel(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
}
);

View File

@@ -0,0 +1,11 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIEvent;
unsafe impl ClassType for UIEvent {
type Super = NSObject;
}
);

View File

@@ -0,0 +1,44 @@
#![deny(unsafe_op_in_unsafe_fn)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use std::os::raw::{c_char, c_int};
use objc2::foundation::NSString;
mod application;
mod coordinate_space;
mod device;
mod event;
mod responder;
mod screen;
mod screen_mode;
mod touch;
mod trait_collection;
mod view;
mod view_controller;
mod window;
pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::UIDevice;
pub(crate) use self::event::UIEvent;
pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode;
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)]
pub(crate) use self::view::{UIEdgeInsets, UIView};
pub(crate) use self::view_controller::{UIInterfaceOrientationMask, UIViewController};
pub(crate) use self::window::UIWindow;
#[link(name = "UIKit", kind = "framework")]
extern "C" {
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
principalClassName: Option<&NSString>,
delegateClassName: Option<&NSString>,
) -> c_int;
}

View File

@@ -0,0 +1,11 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIResponder;
unsafe impl ClassType for UIResponder {
type Super = NSObject;
}
);

View File

@@ -0,0 +1,79 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UICoordinateSpace, UIScreenMode};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIScreen;
unsafe impl ClassType for UIScreen {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIScreen {
pub fn main(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mainScreen] }
}
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self, Shared>, Shared> {
unsafe { msg_send_id![Self::class(), screens] }
}
#[sel(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(scale)]
pub fn scale(&self) -> CGFloat;
#[sel(nativeBounds)]
pub fn nativeBounds(&self) -> CGRect;
#[sel(nativeScale)]
pub fn nativeScale(&self) -> CGFloat;
#[sel(maximumFramesPerSecond)]
pub fn maximumFramesPerSecond(&self) -> NSInteger;
pub fn mirroredScreen(&self) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mirroredScreen] }
}
pub fn preferredMode(&self) -> Option<Id<UIScreenMode, Shared>> {
unsafe { msg_send_id![self, preferredMode] }
}
#[sel(setCurrentMode:)]
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode, Shared>, Shared> {
unsafe { msg_send_id![self, availableModes] }
}
#[sel(setOverscanCompensation:)]
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace, Shared> {
unsafe { msg_send_id![self, coordinateSpace] }
}
}
);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: Self = Self(0);
pub const InsetBounds: Self = Self(1);
pub const None: Self = Self(2);
}

View File

@@ -0,0 +1,18 @@
use objc2::foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIScreenMode;
unsafe impl ClassType for UIScreenMode {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIScreenMode {
#[sel(size)]
pub fn size(&self) -> CGSize;
}
);

View File

@@ -0,0 +1,64 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use super::UIView;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITouch;
unsafe impl ClassType for UITouch {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UITouch {
#[sel(locationInView:)]
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
#[sel(type)]
pub fn type_(&self) -> UITouchType;
#[sel(force)]
pub fn force(&self) -> CGFloat;
#[sel(maximumPossibleForce)]
pub fn maximumPossibleForce(&self) -> CGFloat;
#[sel(altitudeAngle)]
pub fn altitudeAngle(&self) -> CGFloat;
#[sel(phase)]
pub fn phase(&self) -> UITouchPhase;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
unsafe impl Encode for UITouchType {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
unsafe impl Encode for UITouchPhase {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -0,0 +1,32 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITraitCollection;
unsafe impl ClassType for UITraitCollection {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UITraitCollection {
#[sel(forceTouchCapability)]
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
unsafe impl Encode for UIForceTouchCapability {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -0,0 +1,89 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UICoordinateSpace, UIResponder, UIViewController};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIView;
unsafe impl ClassType for UIView {
#[inherits(NSObject)]
type Super = UIResponder;
}
);
extern_methods!(
unsafe impl UIView {
#[sel(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(setBounds:)]
pub fn setBounds(&self, value: CGRect);
#[sel(frame)]
pub fn frame(&self) -> CGRect;
#[sel(setFrame:)]
pub fn setFrame(&self, value: CGRect);
#[sel(contentScaleFactor)]
pub fn contentScaleFactor(&self) -> CGFloat;
#[sel(setContentScaleFactor:)]
pub fn setContentScaleFactor(&self, val: CGFloat);
#[sel(setMultipleTouchEnabled:)]
pub fn setMultipleTouchEnabled(&self, val: bool);
pub fn rootViewController(&self) -> Option<Id<UIViewController, Shared>> {
unsafe { msg_send_id![self, rootViewController] }
}
#[sel(setRootViewController:)]
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
#[sel(convertRect:toCoordinateSpace:)]
pub fn convertRect_toCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(convertRect:fromCoordinateSpace:)]
pub fn convertRect_fromCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(safeAreaInsets)]
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
#[sel(setNeedsDisplay)]
pub fn setNeedsDisplay(&self);
}
);
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
unsafe impl Encode for UIEdgeInsets {
const ENCODING: Encoding = Encoding::Struct(
"UIEdgeInsets",
&[
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
],
);
}

View File

@@ -0,0 +1,56 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSObject, NSUInteger};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIViewController;
unsafe impl ClassType for UIViewController {
#[inherits(NSObject)]
type Super = UIResponder;
}
);
extern_methods!(
unsafe impl UIViewController {
#[sel(attemptRotationToDeviceOrientation)]
pub fn attemptRotationToDeviceOrientation();
#[sel(setNeedsStatusBarAppearanceUpdate)]
pub fn setNeedsStatusBarAppearanceUpdate(&self);
#[sel(setNeedsUpdateOfHomeIndicatorAutoHidden)]
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
#[sel(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
pub fn view(&self) -> Option<Id<UIView, Shared>> {
unsafe { msg_send_id![self, view] }
}
#[sel(setView:)]
pub fn setView(&self, view: Option<&UIView>);
}
);
bitflags! {
#[derive(Clone, Copy)]
pub struct UIInterfaceOrientationMask: NSUInteger {
const Portrait = 1 << 1;
const PortraitUpsideDown = 1 << 2;
const LandscapeRight = 1 << 3;
const LandscapeLeft = 1 << 4;
const Landscape = Self::LandscapeLeft.bits() | Self::LandscapeRight.bits();
const AllButUpsideDown = Self::Landscape.bits() | Self::Portrait.bits();
const All = Self::AllButUpsideDown.bits() | Self::PortraitUpsideDown.bits();
}
}
unsafe impl Encode for UIInterfaceOrientationMask {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -0,0 +1,35 @@
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIScreen, UIView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIWindow;
unsafe impl ClassType for UIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
}
);
extern_methods!(
unsafe impl UIWindow {
pub fn screen(&self) -> Id<UIScreen, Shared> {
unsafe { msg_send_id![self, screen] }
}
#[sel(setScreen:)]
pub fn setScreen(&self, screen: &UIScreen);
#[sel(setHidden:)]
pub fn setHidden(&self, flag: bool);
#[sel(makeKeyAndVisible)]
pub fn makeKeyAndVisible(&self);
#[sel(isKeyWindow)]
pub fn isKeyWindow(&self) -> bool;
}
);

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +1,55 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::VecDeque,
ffi::c_void,
ops::{Deref, DerefMut},
};
use objc::runtime::{Class, Object, BOOL, NO, YES};
use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{class, msg_send};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
platform::ios::{ScreenEdge, ValidOrientations},
platform_impl::platform::{
app_state,
event_loop::{self, EventProxy, EventWrapper},
ffi::{
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
UIRectEdge, UIScreenOverscanCompensation,
},
monitor, view, EventLoopWindowTarget, MonitorHandle,
event_loop::{EventProxy, EventWrapper},
ffi::UIRectEdge,
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
},
window::{
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
},
};
pub struct Inner {
pub window: id,
pub view_controller: id,
pub view: id,
pub(crate) window: Id<WinitUIWindow, Shared>,
pub(crate) view_controller: Id<WinitViewController, Shared>,
pub(crate) view: Id<WinitView, Shared>,
gl_or_metal_backed: bool,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.view, release];
let _: () = msg_send![self.view_controller, release];
let _: () = msg_send![self.window, release];
}
}
}
impl Inner {
pub fn set_title(&self, _title: &str) {
debug!("`Window::set_title` is ignored on iOS")
}
pub fn set_transparent(&self, _transparent: bool) {
debug!("`Window::set_transparent` is ignored on iOS")
}
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe {
let _: () = msg_send![self.window, setHidden: NO];
},
false => unsafe {
let _: () = msg_send![self.window, setHidden: YES];
},
}
self.window.setHidden(!visible)
}
pub fn is_visible(&self) -> Option<bool> {
@@ -77,9 +68,9 @@ impl Inner {
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window);
app_state::queue_gl_or_metal_redraw(self.window.clone());
} else {
let _: () = msg_send![self.view, setNeedsDisplay];
self.view.setNeedsDisplay();
}
}
}
@@ -121,7 +112,7 @@ impl Inner {
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
let _: () = msg_send![self.window, setBounds: bounds];
self.window.setBounds(bounds);
}
}
@@ -161,6 +152,15 @@ impl Inner {
warn!("`Window::set_max_inner_size` is ignored on iOS")
}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
warn!("`Window::set_resize_increments` is ignored on iOS")
}
pub fn set_resizable(&self, _resizable: bool) {
warn!("`Window::set_resizable` is ignored on iOS")
}
@@ -170,11 +170,19 @@ impl Inner {
false
}
#[inline]
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
warn!("`Window::set_enabled_buttons` is ignored on iOS");
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
warn!("`Window::enabled_buttons` is ignored on iOS");
WindowButtons::all()
}
pub fn scale_factor(&self) -> f64 {
unsafe {
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
hidpi as _
}
self.view.contentScaleFactor() as _
}
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
@@ -197,6 +205,10 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -205,6 +217,11 @@ impl Inner {
warn!("`Window::set_minimized` is ignored on iOS")
}
pub fn is_minimized(&self) -> Option<bool> {
warn!("`Window::is_minimized` is ignored on iOS");
None
}
pub fn set_maximized(&self, _maximized: bool) {
warn!("`Window::set_maximized` is ignored on iOS")
}
@@ -214,49 +231,42 @@ impl Inner {
false
}
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe {
let uiscreen = match monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
let _: () =
msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
uiscreen
}
Some(Fullscreen::Borderless(monitor)) => monitor
.unwrap_or_else(|| self.current_monitor_inner())
.ui_screen() as id,
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
};
// this is pretty slow on iOS, so avoid doing it if we can
let current: id = msg_send![self.window, screen];
if uiscreen != current {
let _: () = msg_send![self.window, setScreen: uiscreen];
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen();
uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
uiscreen.clone()
}
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
};
let bounds: CGRect = msg_send![uiscreen, bounds];
let _: () = msg_send![self.window, setFrame: bounds];
// For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on
// each side
let _: () = msg_send![
uiscreen,
setOverscanCompensation: UIScreenOverscanCompensation::None
];
// this is pretty slow on iOS, so avoid doing it if we can
let current = self.window.screen();
if uiscreen != current {
self.window.setScreen(&uiscreen);
}
let bounds = uiscreen.bounds();
self.window.setFrame(bounds);
// For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on
// each side
uiscreen.setOverscanCompensation(UIScreenOverscanCompensation::None);
}
pub fn fullscreen(&self) -> Option<Fullscreen> {
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
unsafe {
let monitor = self.current_monitor_inner();
let uiscreen = monitor.inner.ui_screen();
let uiscreen = monitor.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
let screen_bounds = uiscreen.bounds();
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
@@ -271,31 +281,32 @@ impl Inner {
}
}
pub fn set_decorations(&self, _decorations: bool) {
warn!("`Window::set_decorations` is ignored on iOS")
}
pub fn set_decorations(&self, _decorations: bool) {}
pub fn is_decorated(&self) -> bool {
warn!("`Window::is_decorated` is ignored on iOS");
true
}
pub fn set_always_on_top(&self, _always_on_top: bool) {
warn!("`Window::set_always_on_top` is ignored on iOS")
pub fn set_window_level(&self, _level: WindowLevel) {
warn!("`Window::set_window_level` is ignored on iOS")
}
pub fn set_window_icon(&self, _icon: Option<Icon>) {
warn!("`Window::set_window_icon` is ignored on iOS")
}
pub fn set_ime_position(&self, _position: Position) {
warn!("`Window::set_ime_position` is ignored on iOS")
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
}
pub fn set_ime_allowed(&self, _allowed: bool) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn focus_window(&self) {
warn!("`Window::set_focus` is ignored on iOS")
}
@@ -305,43 +316,62 @@ impl Inner {
}
// Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> RootMonitorHandle {
unsafe {
let uiscreen: id = msg_send![self.window, screen];
RootMonitorHandle {
inner: MonitorHandle::retained_new(uiscreen),
}
}
fn current_monitor_inner(&self) -> MonitorHandle {
MonitorHandle::new(self.window.screen())
}
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
pub fn current_monitor(&self) -> Option<MonitorHandle> {
Some(self.current_monitor_inner())
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
unsafe { monitor::uiscreens() }
monitor::uiscreens(MainThreadMarker::new().unwrap())
}
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
let monitor = unsafe { monitor::main_uiscreen() };
Some(RootMonitorHandle { inner: monitor })
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
}
pub fn id(&self) -> WindowId {
self.window.into()
self.window.id()
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = UiKitWindowHandle::empty();
window_handle.ui_window = self.window as _;
window_handle.ui_view = self.view as _;
window_handle.ui_view_controller = self.view_controller as _;
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)
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
}
pub fn theme(&self) -> Option<Theme> {
warn!("`Window::theme` is ignored on iOS");
None
}
pub fn has_focus(&self) -> bool {
self.window.isKeyWindow()
}
#[inline]
pub fn set_theme(&self, _theme: Option<Theme>) {
warn!("`Window::set_theme` is ignored on iOS");
}
pub fn title(&self) -> String {
warn!("`Window::title` is ignored on iOS");
String::new()
}
pub fn reset_dead_keys(&self) {
// Noop
}
}
pub struct Window {
@@ -350,9 +380,7 @@ pub struct Window {
impl Drop for Window {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
}
@@ -363,18 +391,14 @@ impl Deref for Window {
type Target = Inner;
fn deref(&self) -> &Inner {
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for Window {
fn deref_mut(&mut self) -> &mut Inner {
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&mut self.inner
}
}
@@ -385,210 +409,174 @@ impl Window {
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
let mtm = MainThreadMarker::new().unwrap();
if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
}
if window_attributes.max_inner_size.is_some() {
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
}
if window_attributes.always_on_top {
warn!("`WindowAttributes::always_on_top` is unsupported on iOS");
}
// TODO: transparency, visible
unsafe {
let screen = match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => {
video_mode.video_mode.monitor.ui_screen() as id
}
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(),
Some(Fullscreen::Borderless(None)) | None => {
monitor::main_uiscreen().ui_screen() as id
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::Borderless(None)) | None => &main_screen,
};
let screen_bounds = screen.bounds();
let frame = match window_attributes.inner_size {
Some(dim) => {
let scale_factor = screen.scale();
let size = dim.to_logical::<f64>(scale_factor as f64);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: size.width as _,
height: size.height as _,
},
}
}
None => screen_bounds,
};
let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = unsafe {
let layer_class = WinitView::layerClass();
let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal || is_gl
};
let view_controller =
WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view);
let window = WinitUIWindow::new(
mtm,
&window_attributes,
&platform_attributes,
frame,
&view_controller,
);
unsafe { app_state::set_key_window(&window) };
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor = view.contentScaleFactor();
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let bounds = view.bounds();
let screen = window.screen();
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 _,
};
let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size {
Some(dim) => {
let scale_factor: CGFloat = msg_send![screen, scale];
let size = dim.to_logical::<f64>(scale_factor as f64);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: size.width as _,
height: size.height as _,
},
}
}
None => screen_bounds,
};
let view = view::create_view(&window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = {
let view_class: *const Class = msg_send![view, class];
let layer_class: *const Class = msg_send![view_class, layerClass];
let is_metal: BOOL =
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal == YES || is_gl == YES
};
let view_controller =
view::create_view_controller(&window_attributes, &platform_attributes, view);
let window = view::create_window(
&window_attributes,
&platform_attributes,
frame,
view_controller,
);
let result = Window {
inner: Inner {
window,
view_controller,
view,
gl_or_metal_backed,
},
};
app_state::set_key_window(window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![view, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window_id: window,
window: window.clone(),
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id: RootWindowId(window.into()),
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
Ok(result)
}
Ok(Window {
inner: Inner {
window,
view_controller,
view,
gl_or_metal_backed,
},
})
}
}
// WindowExtIOS
impl Inner {
pub fn ui_window(&self) -> id {
self.window
pub fn ui_window(&self) -> *mut c_void {
Id::as_ptr(&self.window) as *mut c_void
}
pub fn ui_view_controller(&self) -> id {
self.view_controller
pub fn ui_view_controller(&self) -> *mut c_void {
Id::as_ptr(&self.view_controller) as *mut c_void
}
pub fn ui_view(&self) -> id {
self.view
pub fn ui_view(&self) -> *mut c_void {
Id::as_ptr(&self.view) as *mut c_void
}
pub fn set_scale_factor(&self, scale_factor: f64) {
unsafe {
assert!(
dpi::validate_scale_factor(scale_factor),
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
);
let scale_factor = scale_factor as CGFloat;
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor];
}
assert!(
dpi::validate_scale_factor(scale_factor),
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
);
let scale_factor = scale_factor as CGFloat;
self.view.setContentScaleFactor(scale_factor);
}
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
unsafe {
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
valid_orientations,
idiom,
);
msg_send![
self.view_controller,
setSupportedInterfaceOrientations: supported_orientations
]
}
self.view_controller.set_supported_interface_orientations(
MainThreadMarker::new().unwrap(),
valid_orientations,
);
}
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
unsafe {
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
let _: () = msg_send![
self.view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
}
self.view_controller
.setPrefersHomeIndicatorAutoHidden(hidden);
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
unsafe {
let _: () = msg_send![
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
}
self.view_controller
.setPreferredScreenEdgesDeferringSystemGestures(edges);
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
unsafe {
let status_bar_hidden = if hidden { YES } else { NO };
let _: () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
}
self.view_controller.setPrefersStatusBarHidden(hidden);
}
}
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(msg_send![self.window, bounds])
self.rect_to_screen_space(self.window.bounds())
}
// requires main thread
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space]
} else {
rect
}
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_toCoordinateSpace(rect, &screen_space)
}
// requires main thread
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space]
} else {
rect
}
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_fromCoordinateSpace(rect, &screen_space)
}
// requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds: CGRect = msg_send![self.window, bounds];
let bounds = self.window.bounds();
if app_state::os_capabilities().safe_area {
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
let safe_area = self.window.safeAreaInsets();
let safe_bounds = CGRect {
origin: CGPoint {
x: bounds.origin.x + safe_area.left,
@@ -602,13 +590,11 @@ impl Inner {
self.rect_to_screen_space(safe_bounds)
} else {
let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame: CGRect = {
let app: id = msg_send![class!(UIApplication), sharedApplication];
assert!(
!app.is_null(),
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"
let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
);
msg_send![app, statusBarFrame]
app.statusBarFrame()
};
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
(screen_frame.origin.y, screen_frame.size.height)
@@ -634,7 +620,7 @@ impl Inner {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId {
window: id,
window: *mut WinitUIWindow,
}
impl WindowId {
@@ -670,39 +656,11 @@ impl From<&Object> for WindowId {
}
}
impl From<&mut Object> for WindowId {
fn from(window: &mut Object) -> WindowId {
WindowId {
window: window as _,
}
}
}
impl From<id> for WindowId {
fn from(window: id) -> WindowId {
WindowId { window }
}
}
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
pub scale_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> PlatformSpecificWindowBuilderAttributes {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
scale_factor: None,
valid_orientations: Default::default(),
prefers_home_indicator_hidden: false,
prefers_status_bar_hidden: false,
preferred_screen_edges_deferring_system_gestures: Default::default(),
}
}
}

View File

@@ -0,0 +1,887 @@
//! Convert XKB keys to Winit keys.
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
/// Map the raw X11-style keycode to the `KeyCode` enum.
///
/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses.
pub fn raw_keycode_to_keycode(keycode: u32) -> KeyCode {
scancode_to_keycode(keycode.saturating_sub(8))
}
/// Map the linux scancode to Keycode.
///
/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode.
pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
// The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as
// libxkbcommon's documentation seems to suggest that the keycode values we're interested in
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
// I can only hope they agree on what the keycodes mean.
//
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are
// difficult to test the correctness of, or discover the purpose of. Because of this, they've
// either been commented out here, or not included at all.
match scancode {
0 => KeyCode::Unidentified(NativeKeyCode::Xkb(0)),
1 => KeyCode::Escape,
2 => KeyCode::Digit1,
3 => KeyCode::Digit2,
4 => KeyCode::Digit3,
5 => KeyCode::Digit4,
6 => KeyCode::Digit5,
7 => KeyCode::Digit6,
8 => KeyCode::Digit7,
9 => KeyCode::Digit8,
10 => KeyCode::Digit9,
11 => KeyCode::Digit0,
12 => KeyCode::Minus,
13 => KeyCode::Equal,
14 => KeyCode::Backspace,
15 => KeyCode::Tab,
16 => KeyCode::KeyQ,
17 => KeyCode::KeyW,
18 => KeyCode::KeyE,
19 => KeyCode::KeyR,
20 => KeyCode::KeyT,
21 => KeyCode::KeyY,
22 => KeyCode::KeyU,
23 => KeyCode::KeyI,
24 => KeyCode::KeyO,
25 => KeyCode::KeyP,
26 => KeyCode::BracketLeft,
27 => KeyCode::BracketRight,
28 => KeyCode::Enter,
29 => KeyCode::ControlLeft,
30 => KeyCode::KeyA,
31 => KeyCode::KeyS,
32 => KeyCode::KeyD,
33 => KeyCode::KeyF,
34 => KeyCode::KeyG,
35 => KeyCode::KeyH,
36 => KeyCode::KeyJ,
37 => KeyCode::KeyK,
38 => KeyCode::KeyL,
39 => KeyCode::Semicolon,
40 => KeyCode::Quote,
41 => KeyCode::Backquote,
42 => KeyCode::ShiftLeft,
43 => KeyCode::Backslash,
44 => KeyCode::KeyZ,
45 => KeyCode::KeyX,
46 => KeyCode::KeyC,
47 => KeyCode::KeyV,
48 => KeyCode::KeyB,
49 => KeyCode::KeyN,
50 => KeyCode::KeyM,
51 => KeyCode::Comma,
52 => KeyCode::Period,
53 => KeyCode::Slash,
54 => KeyCode::ShiftRight,
55 => KeyCode::NumpadMultiply,
56 => KeyCode::AltLeft,
57 => KeyCode::Space,
58 => KeyCode::CapsLock,
59 => KeyCode::F1,
60 => KeyCode::F2,
61 => KeyCode::F3,
62 => KeyCode::F4,
63 => KeyCode::F5,
64 => KeyCode::F6,
65 => KeyCode::F7,
66 => KeyCode::F8,
67 => KeyCode::F9,
68 => KeyCode::F10,
69 => KeyCode::NumLock,
70 => KeyCode::ScrollLock,
71 => KeyCode::Numpad7,
72 => KeyCode::Numpad8,
73 => KeyCode::Numpad9,
74 => KeyCode::NumpadSubtract,
75 => KeyCode::Numpad4,
76 => KeyCode::Numpad5,
77 => KeyCode::Numpad6,
78 => KeyCode::NumpadAdd,
79 => KeyCode::Numpad1,
80 => KeyCode::Numpad2,
81 => KeyCode::Numpad3,
82 => KeyCode::Numpad0,
83 => KeyCode::NumpadDecimal,
85 => KeyCode::Lang5,
86 => KeyCode::IntlBackslash,
87 => KeyCode::F11,
88 => KeyCode::F12,
89 => KeyCode::IntlRo,
90 => KeyCode::Lang3,
91 => KeyCode::Lang4,
92 => KeyCode::Convert,
93 => KeyCode::KanaMode,
94 => KeyCode::NonConvert,
// 95 => KeyCode::KPJPCOMMA,
96 => KeyCode::NumpadEnter,
97 => KeyCode::ControlRight,
98 => KeyCode::NumpadDivide,
99 => KeyCode::PrintScreen,
100 => KeyCode::AltRight,
// 101 => KeyCode::LINEFEED,
102 => KeyCode::Home,
103 => KeyCode::ArrowUp,
104 => KeyCode::PageUp,
105 => KeyCode::ArrowLeft,
106 => KeyCode::ArrowRight,
107 => KeyCode::End,
108 => KeyCode::ArrowDown,
109 => KeyCode::PageDown,
110 => KeyCode::Insert,
111 => KeyCode::Delete,
// 112 => KeyCode::MACRO,
113 => KeyCode::AudioVolumeMute,
114 => KeyCode::AudioVolumeDown,
115 => KeyCode::AudioVolumeUp,
// 116 => KeyCode::POWER,
117 => KeyCode::NumpadEqual,
// 118 => KeyCode::KPPLUSMINUS,
119 => KeyCode::Pause,
// 120 => KeyCode::SCALE,
121 => KeyCode::NumpadComma,
122 => KeyCode::Lang1,
123 => KeyCode::Lang2,
124 => KeyCode::IntlYen,
125 => KeyCode::SuperLeft,
126 => KeyCode::SuperRight,
127 => KeyCode::ContextMenu,
// 128 => KeyCode::STOP,
// 129 => KeyCode::AGAIN,
// 130 => KeyCode::PROPS,
// 131 => KeyCode::UNDO,
// 132 => KeyCode::FRONT,
// 133 => KeyCode::COPY,
// 134 => KeyCode::OPEN,
// 135 => KeyCode::PASTE,
// 136 => KeyCode::FIND,
// 137 => KeyCode::CUT,
// 138 => KeyCode::HELP,
// 139 => KeyCode::MENU,
// 140 => KeyCode::CALC,
// 141 => KeyCode::SETUP,
// 142 => KeyCode::SLEEP,
// 143 => KeyCode::WAKEUP,
// 144 => KeyCode::FILE,
// 145 => KeyCode::SENDFILE,
// 146 => KeyCode::DELETEFILE,
// 147 => KeyCode::XFER,
// 148 => KeyCode::PROG1,
// 149 => KeyCode::PROG2,
// 150 => KeyCode::WWW,
// 151 => KeyCode::MSDOS,
// 152 => KeyCode::COFFEE,
// 153 => KeyCode::ROTATE_DISPLAY,
// 154 => KeyCode::CYCLEWINDOWS,
// 155 => KeyCode::MAIL,
// 156 => KeyCode::BOOKMARKS,
// 157 => KeyCode::COMPUTER,
// 158 => KeyCode::BACK,
// 159 => KeyCode::FORWARD,
// 160 => KeyCode::CLOSECD,
// 161 => KeyCode::EJECTCD,
// 162 => KeyCode::EJECTCLOSECD,
163 => KeyCode::MediaTrackNext,
164 => KeyCode::MediaPlayPause,
165 => KeyCode::MediaTrackPrevious,
166 => KeyCode::MediaStop,
// 167 => KeyCode::RECORD,
// 168 => KeyCode::REWIND,
// 169 => KeyCode::PHONE,
// 170 => KeyCode::ISO,
// 171 => KeyCode::CONFIG,
// 172 => KeyCode::HOMEPAGE,
// 173 => KeyCode::REFRESH,
// 174 => KeyCode::EXIT,
// 175 => KeyCode::MOVE,
// 176 => KeyCode::EDIT,
// 177 => KeyCode::SCROLLUP,
// 178 => KeyCode::SCROLLDOWN,
// 179 => KeyCode::KPLEFTPAREN,
// 180 => KeyCode::KPRIGHTPAREN,
// 181 => KeyCode::NEW,
// 182 => KeyCode::REDO,
183 => KeyCode::F13,
184 => KeyCode::F14,
185 => KeyCode::F15,
186 => KeyCode::F16,
187 => KeyCode::F17,
188 => KeyCode::F18,
189 => KeyCode::F19,
190 => KeyCode::F20,
191 => KeyCode::F21,
192 => KeyCode::F22,
193 => KeyCode::F23,
194 => KeyCode::F24,
// 200 => KeyCode::PLAYCD,
// 201 => KeyCode::PAUSECD,
// 202 => KeyCode::PROG3,
// 203 => KeyCode::PROG4,
// 204 => KeyCode::DASHBOARD,
// 205 => KeyCode::SUSPEND,
// 206 => KeyCode::CLOSE,
// 207 => KeyCode::PLAY,
// 208 => KeyCode::FASTFORWARD,
// 209 => KeyCode::BASSBOOST,
// 210 => KeyCode::PRINT,
// 211 => KeyCode::HP,
// 212 => KeyCode::CAMERA,
// 213 => KeyCode::SOUND,
// 214 => KeyCode::QUESTION,
// 215 => KeyCode::EMAIL,
// 216 => KeyCode::CHAT,
// 217 => KeyCode::SEARCH,
// 218 => KeyCode::CONNECT,
// 219 => KeyCode::FINANCE,
// 220 => KeyCode::SPORT,
// 221 => KeyCode::SHOP,
// 222 => KeyCode::ALTERASE,
// 223 => KeyCode::CANCEL,
// 224 => KeyCode::BRIGHTNESSDOW,
// 225 => KeyCode::BRIGHTNESSU,
// 226 => KeyCode::MEDIA,
// 227 => KeyCode::SWITCHVIDEOMODE,
// 228 => KeyCode::KBDILLUMTOGGLE,
// 229 => KeyCode::KBDILLUMDOWN,
// 230 => KeyCode::KBDILLUMUP,
// 231 => KeyCode::SEND,
// 232 => KeyCode::REPLY,
// 233 => KeyCode::FORWARDMAIL,
// 234 => KeyCode::SAVE,
// 235 => KeyCode::DOCUMENTS,
// 236 => KeyCode::BATTERY,
// 237 => KeyCode::BLUETOOTH,
// 238 => KeyCode::WLAN,
// 239 => KeyCode::UWB,
240 => KeyCode::Unidentified(NativeKeyCode::Unidentified),
// 241 => KeyCode::VIDEO_NEXT,
// 242 => KeyCode::VIDEO_PREV,
// 243 => KeyCode::BRIGHTNESS_CYCLE,
// 244 => KeyCode::BRIGHTNESS_AUTO,
// 245 => KeyCode::DISPLAY_OFF,
// 246 => KeyCode::WWAN,
// 247 => KeyCode::RFKILL,
// 248 => KeyCode::KEY_MICMUTE,
_ => KeyCode::Unidentified(NativeKeyCode::Xkb(scancode)),
}
}
pub fn keycode_to_scancode(keycode: KeyCode) -> Option<u32> {
match keycode {
KeyCode::Unidentified(NativeKeyCode::Unidentified) => Some(240),
KeyCode::Unidentified(NativeKeyCode::Xkb(raw)) => Some(raw),
KeyCode::Escape => Some(1),
KeyCode::Digit1 => Some(2),
KeyCode::Digit2 => Some(3),
KeyCode::Digit3 => Some(4),
KeyCode::Digit4 => Some(5),
KeyCode::Digit5 => Some(6),
KeyCode::Digit6 => Some(7),
KeyCode::Digit7 => Some(8),
KeyCode::Digit8 => Some(9),
KeyCode::Digit9 => Some(10),
KeyCode::Digit0 => Some(11),
KeyCode::Minus => Some(12),
KeyCode::Equal => Some(13),
KeyCode::Backspace => Some(14),
KeyCode::Tab => Some(15),
KeyCode::KeyQ => Some(16),
KeyCode::KeyW => Some(17),
KeyCode::KeyE => Some(18),
KeyCode::KeyR => Some(19),
KeyCode::KeyT => Some(20),
KeyCode::KeyY => Some(21),
KeyCode::KeyU => Some(22),
KeyCode::KeyI => Some(23),
KeyCode::KeyO => Some(24),
KeyCode::KeyP => Some(25),
KeyCode::BracketLeft => Some(26),
KeyCode::BracketRight => Some(27),
KeyCode::Enter => Some(28),
KeyCode::ControlLeft => Some(29),
KeyCode::KeyA => Some(30),
KeyCode::KeyS => Some(31),
KeyCode::KeyD => Some(32),
KeyCode::KeyF => Some(33),
KeyCode::KeyG => Some(34),
KeyCode::KeyH => Some(35),
KeyCode::KeyJ => Some(36),
KeyCode::KeyK => Some(37),
KeyCode::KeyL => Some(38),
KeyCode::Semicolon => Some(39),
KeyCode::Quote => Some(40),
KeyCode::Backquote => Some(41),
KeyCode::ShiftLeft => Some(42),
KeyCode::Backslash => Some(43),
KeyCode::KeyZ => Some(44),
KeyCode::KeyX => Some(45),
KeyCode::KeyC => Some(46),
KeyCode::KeyV => Some(47),
KeyCode::KeyB => Some(48),
KeyCode::KeyN => Some(49),
KeyCode::KeyM => Some(50),
KeyCode::Comma => Some(51),
KeyCode::Period => Some(52),
KeyCode::Slash => Some(53),
KeyCode::ShiftRight => Some(54),
KeyCode::NumpadMultiply => Some(55),
KeyCode::AltLeft => Some(56),
KeyCode::Space => Some(57),
KeyCode::CapsLock => Some(58),
KeyCode::F1 => Some(59),
KeyCode::F2 => Some(60),
KeyCode::F3 => Some(61),
KeyCode::F4 => Some(62),
KeyCode::F5 => Some(63),
KeyCode::F6 => Some(64),
KeyCode::F7 => Some(65),
KeyCode::F8 => Some(66),
KeyCode::F9 => Some(67),
KeyCode::F10 => Some(68),
KeyCode::NumLock => Some(69),
KeyCode::ScrollLock => Some(70),
KeyCode::Numpad7 => Some(71),
KeyCode::Numpad8 => Some(72),
KeyCode::Numpad9 => Some(73),
KeyCode::NumpadSubtract => Some(74),
KeyCode::Numpad4 => Some(75),
KeyCode::Numpad5 => Some(76),
KeyCode::Numpad6 => Some(77),
KeyCode::NumpadAdd => Some(78),
KeyCode::Numpad1 => Some(79),
KeyCode::Numpad2 => Some(80),
KeyCode::Numpad3 => Some(81),
KeyCode::Numpad0 => Some(82),
KeyCode::NumpadDecimal => Some(83),
KeyCode::Lang5 => Some(85),
KeyCode::IntlBackslash => Some(86),
KeyCode::F11 => Some(87),
KeyCode::F12 => Some(88),
KeyCode::IntlRo => Some(89),
KeyCode::Lang3 => Some(90),
KeyCode::Lang4 => Some(91),
KeyCode::Convert => Some(92),
KeyCode::KanaMode => Some(93),
KeyCode::NonConvert => Some(94),
KeyCode::NumpadEnter => Some(96),
KeyCode::ControlRight => Some(97),
KeyCode::NumpadDivide => Some(98),
KeyCode::PrintScreen => Some(99),
KeyCode::AltRight => Some(100),
KeyCode::Home => Some(102),
KeyCode::ArrowUp => Some(103),
KeyCode::PageUp => Some(104),
KeyCode::ArrowLeft => Some(105),
KeyCode::ArrowRight => Some(106),
KeyCode::End => Some(107),
KeyCode::ArrowDown => Some(108),
KeyCode::PageDown => Some(109),
KeyCode::Insert => Some(110),
KeyCode::Delete => Some(111),
KeyCode::AudioVolumeMute => Some(113),
KeyCode::AudioVolumeDown => Some(114),
KeyCode::AudioVolumeUp => Some(115),
KeyCode::NumpadEqual => Some(117),
KeyCode::Pause => Some(119),
KeyCode::NumpadComma => Some(121),
KeyCode::Lang1 => Some(122),
KeyCode::Lang2 => Some(123),
KeyCode::IntlYen => Some(124),
KeyCode::SuperLeft => Some(125),
KeyCode::SuperRight => Some(126),
KeyCode::ContextMenu => Some(127),
KeyCode::MediaTrackNext => Some(163),
KeyCode::MediaPlayPause => Some(164),
KeyCode::MediaTrackPrevious => Some(165),
KeyCode::MediaStop => Some(166),
KeyCode::F13 => Some(183),
KeyCode::F14 => Some(184),
KeyCode::F15 => Some(185),
KeyCode::F16 => Some(186),
KeyCode::F17 => Some(187),
KeyCode::F18 => Some(188),
KeyCode::F19 => Some(189),
KeyCode::F20 => Some(190),
KeyCode::F21 => Some(191),
KeyCode::F22 => Some(192),
KeyCode::F23 => Some(193),
KeyCode::F24 => Some(194),
_ => None,
}
}
pub fn keysym_to_key(keysym: u32) -> Key {
use xkbcommon_dl::keysyms;
match keysym {
// TTY function keys
keysyms::BackSpace => Key::Backspace,
keysyms::Tab => Key::Tab,
// keysyms::Linefeed => Key::Linefeed,
keysyms::Clear => Key::Clear,
keysyms::Return => Key::Enter,
keysyms::Pause => Key::Pause,
keysyms::Scroll_Lock => Key::ScrollLock,
keysyms::Sys_Req => Key::PrintScreen,
keysyms::Escape => Key::Escape,
keysyms::Delete => Key::Delete,
// IME keys
keysyms::Multi_key => Key::Compose,
keysyms::Codeinput => Key::CodeInput,
keysyms::SingleCandidate => Key::SingleCandidate,
keysyms::MultipleCandidate => Key::AllCandidates,
keysyms::PreviousCandidate => Key::PreviousCandidate,
// Japanese keys
keysyms::Kanji => Key::KanjiMode,
keysyms::Muhenkan => Key::NonConvert,
keysyms::Henkan_Mode => Key::Convert,
keysyms::Romaji => Key::Romaji,
keysyms::Hiragana => Key::Hiragana,
keysyms::Hiragana_Katakana => Key::HiraganaKatakana,
keysyms::Zenkaku => Key::Zenkaku,
keysyms::Hankaku => Key::Hankaku,
keysyms::Zenkaku_Hankaku => Key::ZenkakuHankaku,
// keysyms::Touroku => Key::Touroku,
// keysyms::Massyo => Key::Massyo,
keysyms::Kana_Lock => Key::KanaMode,
keysyms::Kana_Shift => Key::KanaMode,
keysyms::Eisu_Shift => Key::Alphanumeric,
keysyms::Eisu_toggle => Key::Alphanumeric,
// NOTE: The next three items are aliases for values we've already mapped.
// keysyms::Kanji_Bangou => Key::CodeInput,
// keysyms::Zen_Koho => Key::AllCandidates,
// keysyms::Mae_Koho => Key::PreviousCandidate,
// Cursor control & motion
keysyms::Home => Key::Home,
keysyms::Left => Key::ArrowLeft,
keysyms::Up => Key::ArrowUp,
keysyms::Right => Key::ArrowRight,
keysyms::Down => Key::ArrowDown,
// keysyms::Prior => Key::PageUp,
keysyms::Page_Up => Key::PageUp,
// keysyms::Next => Key::PageDown,
keysyms::Page_Down => Key::PageDown,
keysyms::End => Key::End,
// keysyms::Begin => Key::Begin,
// Misc. functions
keysyms::Select => Key::Select,
keysyms::Print => Key::PrintScreen,
keysyms::Execute => Key::Execute,
keysyms::Insert => Key::Insert,
keysyms::Undo => Key::Undo,
keysyms::Redo => Key::Redo,
keysyms::Menu => Key::ContextMenu,
keysyms::Find => Key::Find,
keysyms::Cancel => Key::Cancel,
keysyms::Help => Key::Help,
keysyms::Break => Key::Pause,
keysyms::Mode_switch => Key::ModeChange,
// keysyms::script_switch => Key::ModeChange,
keysyms::Num_Lock => Key::NumLock,
// Keypad keys
// keysyms::KP_Space => Key::Character(" "),
keysyms::KP_Tab => Key::Tab,
keysyms::KP_Enter => Key::Enter,
keysyms::KP_F1 => Key::F1,
keysyms::KP_F2 => Key::F2,
keysyms::KP_F3 => Key::F3,
keysyms::KP_F4 => Key::F4,
keysyms::KP_Home => Key::Home,
keysyms::KP_Left => Key::ArrowLeft,
keysyms::KP_Up => Key::ArrowLeft,
keysyms::KP_Right => Key::ArrowRight,
keysyms::KP_Down => Key::ArrowDown,
// keysyms::KP_Prior => Key::PageUp,
keysyms::KP_Page_Up => Key::PageUp,
// keysyms::KP_Next => Key::PageDown,
keysyms::KP_Page_Down => Key::PageDown,
keysyms::KP_End => Key::End,
// This is the key labeled "5" on the numpad when NumLock is off.
// keysyms::KP_Begin => Key::Begin,
keysyms::KP_Insert => Key::Insert,
keysyms::KP_Delete => Key::Delete,
// keysyms::KP_Equal => Key::Equal,
// keysyms::KP_Multiply => Key::Multiply,
// keysyms::KP_Add => Key::Add,
// keysyms::KP_Separator => Key::Separator,
// keysyms::KP_Subtract => Key::Subtract,
// keysyms::KP_Decimal => Key::Decimal,
// keysyms::KP_Divide => Key::Divide,
// keysyms::KP_0 => Key::Character("0"),
// keysyms::KP_1 => Key::Character("1"),
// keysyms::KP_2 => Key::Character("2"),
// keysyms::KP_3 => Key::Character("3"),
// keysyms::KP_4 => Key::Character("4"),
// keysyms::KP_5 => Key::Character("5"),
// keysyms::KP_6 => Key::Character("6"),
// keysyms::KP_7 => Key::Character("7"),
// keysyms::KP_8 => Key::Character("8"),
// keysyms::KP_9 => Key::Character("9"),
// Function keys
keysyms::F1 => Key::F1,
keysyms::F2 => Key::F2,
keysyms::F3 => Key::F3,
keysyms::F4 => Key::F4,
keysyms::F5 => Key::F5,
keysyms::F6 => Key::F6,
keysyms::F7 => Key::F7,
keysyms::F8 => Key::F8,
keysyms::F9 => Key::F9,
keysyms::F10 => Key::F10,
keysyms::F11 => Key::F11,
keysyms::F12 => Key::F12,
keysyms::F13 => Key::F13,
keysyms::F14 => Key::F14,
keysyms::F15 => Key::F15,
keysyms::F16 => Key::F16,
keysyms::F17 => Key::F17,
keysyms::F18 => Key::F18,
keysyms::F19 => Key::F19,
keysyms::F20 => Key::F20,
keysyms::F21 => Key::F21,
keysyms::F22 => Key::F22,
keysyms::F23 => Key::F23,
keysyms::F24 => Key::F24,
keysyms::F25 => Key::F25,
keysyms::F26 => Key::F26,
keysyms::F27 => Key::F27,
keysyms::F28 => Key::F28,
keysyms::F29 => Key::F29,
keysyms::F30 => Key::F30,
keysyms::F31 => Key::F31,
keysyms::F32 => Key::F32,
keysyms::F33 => Key::F33,
keysyms::F34 => Key::F34,
keysyms::F35 => Key::F35,
// Modifiers
keysyms::Shift_L => Key::Shift,
keysyms::Shift_R => Key::Shift,
keysyms::Control_L => Key::Control,
keysyms::Control_R => Key::Control,
keysyms::Caps_Lock => Key::CapsLock,
// keysyms::Shift_Lock => Key::ShiftLock,
// keysyms::Meta_L => Key::Meta,
// keysyms::Meta_R => Key::Meta,
keysyms::Alt_L => Key::Alt,
keysyms::Alt_R => Key::Alt,
keysyms::Super_L => Key::Super,
keysyms::Super_R => Key::Super,
keysyms::Hyper_L => Key::Hyper,
keysyms::Hyper_R => Key::Hyper,
// XKB function and modifier keys
// keysyms::ISO_Lock => Key::IsoLock,
// keysyms::ISO_Level2_Latch => Key::IsoLevel2Latch,
keysyms::ISO_Level3_Shift => Key::AltGraph,
keysyms::ISO_Level3_Latch => Key::AltGraph,
keysyms::ISO_Level3_Lock => Key::AltGraph,
// keysyms::ISO_Level5_Shift => Key::IsoLevel5Shift,
// keysyms::ISO_Level5_Latch => Key::IsoLevel5Latch,
// keysyms::ISO_Level5_Lock => Key::IsoLevel5Lock,
// keysyms::ISO_Group_Shift => Key::IsoGroupShift,
// keysyms::ISO_Group_Latch => Key::IsoGroupLatch,
// keysyms::ISO_Group_Lock => Key::IsoGroupLock,
keysyms::ISO_Next_Group => Key::GroupNext,
// keysyms::ISO_Next_Group_Lock => Key::GroupNextLock,
keysyms::ISO_Prev_Group => Key::GroupPrevious,
// keysyms::ISO_Prev_Group_Lock => Key::GroupPreviousLock,
keysyms::ISO_First_Group => Key::GroupFirst,
// keysyms::ISO_First_Group_Lock => Key::GroupFirstLock,
keysyms::ISO_Last_Group => Key::GroupLast,
// keysyms::ISO_Last_Group_Lock => Key::GroupLastLock,
//
keysyms::ISO_Left_Tab => Key::Tab,
// keysyms::ISO_Move_Line_Up => Key::IsoMoveLineUp,
// keysyms::ISO_Move_Line_Down => Key::IsoMoveLineDown,
// keysyms::ISO_Partial_Line_Up => Key::IsoPartialLineUp,
// keysyms::ISO_Partial_Line_Down => Key::IsoPartialLineDown,
// keysyms::ISO_Partial_Space_Left => Key::IsoPartialSpaceLeft,
// keysyms::ISO_Partial_Space_Right => Key::IsoPartialSpaceRight,
// keysyms::ISO_Set_Margin_Left => Key::IsoSetMarginLeft,
// keysyms::ISO_Set_Margin_Right => Key::IsoSetMarginRight,
// keysyms::ISO_Release_Margin_Left => Key::IsoReleaseMarginLeft,
// keysyms::ISO_Release_Margin_Right => Key::IsoReleaseMarginRight,
// keysyms::ISO_Release_Both_Margins => Key::IsoReleaseBothMargins,
// keysyms::ISO_Fast_Cursor_Left => Key::IsoFastCursorLeft,
// keysyms::ISO_Fast_Cursor_Right => Key::IsoFastCursorRight,
// keysyms::ISO_Fast_Cursor_Up => Key::IsoFastCursorUp,
// keysyms::ISO_Fast_Cursor_Down => Key::IsoFastCursorDown,
// keysyms::ISO_Continuous_Underline => Key::IsoContinuousUnderline,
// keysyms::ISO_Discontinuous_Underline => Key::IsoDiscontinuousUnderline,
// keysyms::ISO_Emphasize => Key::IsoEmphasize,
// keysyms::ISO_Center_Object => Key::IsoCenterObject,
keysyms::ISO_Enter => Key::Enter,
// dead_grave..dead_currency
// dead_lowline..dead_longsolidusoverlay
// dead_a..dead_capital_schwa
// dead_greek
// First_Virtual_Screen..Terminate_Server
// AccessX_Enable..AudibleBell_Enable
// Pointer_Left..Pointer_Drag5
// Pointer_EnableKeys..Pointer_DfltBtnPrev
// ch..C_H
// 3270 terminal keys
// keysyms::3270_Duplicate => Key::Duplicate,
// keysyms::3270_FieldMark => Key::FieldMark,
// keysyms::3270_Right2 => Key::Right2,
// keysyms::3270_Left2 => Key::Left2,
// keysyms::3270_BackTab => Key::BackTab,
keysyms::_3270_EraseEOF => Key::EraseEof,
// keysyms::3270_EraseInput => Key::EraseInput,
// keysyms::3270_Reset => Key::Reset,
// keysyms::3270_Quit => Key::Quit,
// keysyms::3270_PA1 => Key::Pa1,
// keysyms::3270_PA2 => Key::Pa2,
// keysyms::3270_PA3 => Key::Pa3,
// keysyms::3270_Test => Key::Test,
keysyms::_3270_Attn => Key::Attn,
// keysyms::3270_CursorBlink => Key::CursorBlink,
// keysyms::3270_AltCursor => Key::AltCursor,
// keysyms::3270_KeyClick => Key::KeyClick,
// keysyms::3270_Jump => Key::Jump,
// keysyms::3270_Ident => Key::Ident,
// keysyms::3270_Rule => Key::Rule,
// keysyms::3270_Copy => Key::Copy,
keysyms::_3270_Play => Key::Play,
// keysyms::3270_Setup => Key::Setup,
// keysyms::3270_Record => Key::Record,
// keysyms::3270_ChangeScreen => Key::ChangeScreen,
// keysyms::3270_DeleteWord => Key::DeleteWord,
keysyms::_3270_ExSelect => Key::ExSel,
keysyms::_3270_CursorSelect => Key::CrSel,
keysyms::_3270_PrintScreen => Key::PrintScreen,
keysyms::_3270_Enter => Key::Enter,
keysyms::space => Key::Space,
// exclam..Sinh_kunddaliya
// XFree86
// keysyms::XF86_ModeLock => Key::ModeLock,
// XFree86 - Backlight controls
keysyms::XF86_MonBrightnessUp => Key::BrightnessUp,
keysyms::XF86_MonBrightnessDown => Key::BrightnessDown,
// keysyms::XF86_KbdLightOnOff => Key::LightOnOff,
// keysyms::XF86_KbdBrightnessUp => Key::KeyboardBrightnessUp,
// keysyms::XF86_KbdBrightnessDown => Key::KeyboardBrightnessDown,
// XFree86 - "Internet"
keysyms::XF86_Standby => Key::Standby,
keysyms::XF86_AudioLowerVolume => Key::AudioVolumeDown,
keysyms::XF86_AudioRaiseVolume => Key::AudioVolumeUp,
keysyms::XF86_AudioPlay => Key::MediaPlay,
keysyms::XF86_AudioStop => Key::MediaStop,
keysyms::XF86_AudioPrev => Key::MediaTrackPrevious,
keysyms::XF86_AudioNext => Key::MediaTrackNext,
keysyms::XF86_HomePage => Key::BrowserHome,
keysyms::XF86_Mail => Key::LaunchMail,
// keysyms::XF86_Start => Key::Start,
keysyms::XF86_Search => Key::BrowserSearch,
keysyms::XF86_AudioRecord => Key::MediaRecord,
// XFree86 - PDA
keysyms::XF86_Calculator => Key::LaunchApplication2,
// keysyms::XF86_Memo => Key::Memo,
// keysyms::XF86_ToDoList => Key::ToDoList,
keysyms::XF86_Calendar => Key::LaunchCalendar,
keysyms::XF86_PowerDown => Key::Power,
// keysyms::XF86_ContrastAdjust => Key::AdjustContrast,
// keysyms::XF86_RockerUp => Key::RockerUp,
// keysyms::XF86_RockerDown => Key::RockerDown,
// keysyms::XF86_RockerEnter => Key::RockerEnter,
// XFree86 - More "Internet"
keysyms::XF86_Back => Key::BrowserBack,
keysyms::XF86_Forward => Key::BrowserForward,
// keysyms::XF86_Stop => Key::Stop,
keysyms::XF86_Refresh => Key::BrowserRefresh,
keysyms::XF86_PowerOff => Key::Power,
keysyms::XF86_WakeUp => Key::WakeUp,
keysyms::XF86_Eject => Key::Eject,
keysyms::XF86_ScreenSaver => Key::LaunchScreenSaver,
keysyms::XF86_WWW => Key::LaunchWebBrowser,
keysyms::XF86_Sleep => Key::Standby,
keysyms::XF86_Favorites => Key::BrowserFavorites,
keysyms::XF86_AudioPause => Key::MediaPause,
// keysyms::XF86_AudioMedia => Key::AudioMedia,
keysyms::XF86_MyComputer => Key::LaunchApplication1,
// keysyms::XF86_VendorHome => Key::VendorHome,
// keysyms::XF86_LightBulb => Key::LightBulb,
// keysyms::XF86_Shop => Key::BrowserShop,
// keysyms::XF86_History => Key::BrowserHistory,
// keysyms::XF86_OpenURL => Key::OpenUrl,
// keysyms::XF86_AddFavorite => Key::AddFavorite,
// keysyms::XF86_HotLinks => Key::HotLinks,
// keysyms::XF86_BrightnessAdjust => Key::BrightnessAdjust,
// keysyms::XF86_Finance => Key::BrowserFinance,
// keysyms::XF86_Community => Key::BrowserCommunity,
keysyms::XF86_AudioRewind => Key::MediaRewind,
// keysyms::XF86_BackForward => Key::???,
// XF86_Launch0..XF86_LaunchF
// XF86_ApplicationLeft..XF86_CD
keysyms::XF86_Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :)
// XF86_Clear
keysyms::XF86_Close => Key::Close,
keysyms::XF86_Copy => Key::Copy,
keysyms::XF86_Cut => Key::Cut,
// XF86_Display..XF86_Documents
keysyms::XF86_Excel => Key::LaunchSpreadsheet,
// XF86_Explorer..XF86iTouch
keysyms::XF86_LogOff => Key::LogOff,
// XF86_Market..XF86_MenuPB
keysyms::XF86_MySites => Key::BrowserFavorites,
keysyms::XF86_New => Key::New,
// XF86_News..XF86_OfficeHome
keysyms::XF86_Open => Key::Open,
// XF86_Option
keysyms::XF86_Paste => Key::Paste,
keysyms::XF86_Phone => Key::LaunchPhone,
// XF86_Q
keysyms::XF86_Reply => Key::MailReply,
keysyms::XF86_Reload => Key::BrowserRefresh,
// XF86_RotateWindows..XF86_RotationKB
keysyms::XF86_Save => Key::Save,
// XF86_ScrollUp..XF86_ScrollClick
keysyms::XF86_Send => Key::MailSend,
keysyms::XF86_Spell => Key::SpellCheck,
keysyms::XF86_SplitScreen => Key::SplitScreenToggle,
// XF86_Support..XF86_User2KB
keysyms::XF86_Video => Key::LaunchMediaPlayer,
// XF86_WheelButton
keysyms::XF86_Word => Key::LaunchWordProcessor,
// XF86_Xfer
keysyms::XF86_ZoomIn => Key::ZoomIn,
keysyms::XF86_ZoomOut => Key::ZoomOut,
// XF86_Away..XF86_Messenger
keysyms::XF86_WebCam => Key::LaunchWebCam,
keysyms::XF86_MailForward => Key::MailForward,
// XF86_Pictures
keysyms::XF86_Music => Key::LaunchMusicPlayer,
// XF86_Battery..XF86_UWB
//
keysyms::XF86_AudioForward => Key::MediaFastForward,
// XF86_AudioRepeat
keysyms::XF86_AudioRandomPlay => Key::RandomToggle,
keysyms::XF86_Subtitle => Key::Subtitle,
keysyms::XF86_AudioCycleTrack => Key::MediaAudioTrack,
// XF86_CycleAngle..XF86_Blue
//
keysyms::XF86_Suspend => Key::Standby,
keysyms::XF86_Hibernate => Key::Hibernate,
// XF86_TouchpadToggle..XF86_TouchpadOff
//
keysyms::XF86_AudioMute => Key::AudioVolumeMute,
// XF86_Switch_VT_1..XF86_Switch_VT_12
// XF86_Ungrab..XF86_ClearGrab
keysyms::XF86_Next_VMode => Key::VideoModeNext,
// keysyms::XF86_Prev_VMode => Key::VideoModePrevious,
// XF86_LogWindowTree..XF86_LogGrabInfo
// SunFA_Grave..SunFA_Cedilla
// keysyms::SunF36 => Key::F36 | Key::F11,
// keysyms::SunF37 => Key::F37 | Key::F12,
// keysyms::SunSys_Req => Key::PrintScreen,
// The next couple of xkb (until SunStop) are already handled.
// SunPrint_Screen..SunPageDown
// SunUndo..SunFront
keysyms::SUN_Copy => Key::Copy,
keysyms::SUN_Open => Key::Open,
keysyms::SUN_Paste => Key::Paste,
keysyms::SUN_Cut => Key::Cut,
// SunPowerSwitch
keysyms::SUN_AudioLowerVolume => Key::AudioVolumeDown,
keysyms::SUN_AudioMute => Key::AudioVolumeMute,
keysyms::SUN_AudioRaiseVolume => Key::AudioVolumeUp,
// SUN_VideoDegauss
keysyms::SUN_VideoLowerBrightness => Key::BrightnessDown,
keysyms::SUN_VideoRaiseBrightness => Key::BrightnessUp,
// SunPowerSwitchShift
//
0 => Key::Unidentified(NativeKey::Unidentified),
_ => Key::Unidentified(NativeKey::Xkb(keysym)),
}
}
pub fn keysym_location(keysym: u32) -> KeyLocation {
use xkbcommon_dl::keysyms;
match keysym {
keysyms::Shift_L
| keysyms::Control_L
| keysyms::Meta_L
| keysyms::Alt_L
| keysyms::Super_L
| keysyms::Hyper_L => KeyLocation::Left,
keysyms::Shift_R
| keysyms::Control_R
| keysyms::Meta_R
| keysyms::Alt_R
| keysyms::Super_R
| keysyms::Hyper_R => KeyLocation::Right,
keysyms::KP_0
| keysyms::KP_1
| keysyms::KP_2
| keysyms::KP_3
| keysyms::KP_4
| keysyms::KP_5
| keysyms::KP_6
| keysyms::KP_7
| keysyms::KP_8
| keysyms::KP_9
| keysyms::KP_Space
| keysyms::KP_Tab
| keysyms::KP_Enter
| keysyms::KP_F1
| keysyms::KP_F2
| keysyms::KP_F3
| keysyms::KP_F4
| keysyms::KP_Home
| keysyms::KP_Left
| keysyms::KP_Up
| keysyms::KP_Right
| keysyms::KP_Down
| keysyms::KP_Page_Up
| keysyms::KP_Page_Down
| keysyms::KP_End
| keysyms::KP_Begin
| keysyms::KP_Insert
| keysyms::KP_Delete
| keysyms::KP_Equal
| keysyms::KP_Multiply
| keysyms::KP_Add
| keysyms::KP_Separator
| keysyms::KP_Subtract
| keysyms::KP_Decimal
| keysyms::KP_Divide => KeyLocation::Numpad,
_ => KeyLocation::Standard,
}
}

View File

@@ -0,0 +1,2 @@
pub mod keymap;
pub mod xkb_state;

View File

@@ -0,0 +1,677 @@
use std::convert::TryInto;
use std::env;
use std::ffi::CString;
use std::os::raw::c_char;
use std::os::unix::ffi::OsStringExt;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use once_cell::sync::Lazy;
use smol_str::SmolStr;
use xkbcommon_dl::{
self as ffi, xkb_state_component, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon,
XkbCommonCompose,
};
#[cfg(feature = "wayland")]
use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd};
#[cfg(feature = "x11")]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
use crate::event::KeyEvent;
use crate::platform_impl::common::keymap;
use crate::platform_impl::KeyEventExtra;
use crate::{
event::ElementState,
keyboard::{Key, KeyCode, KeyLocation},
};
// TODO: Wire this up without using a static `AtomicBool`.
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
#[inline(always)]
pub fn reset_dead_keys() {
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
}
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
#[cfg(feature = "x11")]
static XKBXH: Lazy<&'static ffi::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
#[derive(Debug)]
pub struct KbdState {
#[cfg(feature = "x11")]
xcb_connection: *mut xcb_connection_t,
xkb_context: *mut ffi::xkb_context,
xkb_keymap: *mut ffi::xkb_keymap,
xkb_state: *mut ffi::xkb_state,
xkb_compose_table: *mut ffi::xkb_compose_table,
xkb_compose_state: *mut ffi::xkb_compose_state,
xkb_compose_state_2: *mut ffi::xkb_compose_state,
mods_state: ModifiersState,
#[cfg(feature = "x11")]
pub core_keyboard_id: i32,
scratch_buffer: Vec<u8>,
}
impl KbdState {
pub fn update_modifiers(
&mut self,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
depressed_group: u32,
latched_group: u32,
locked_group: u32,
) {
if !self.ready() {
return;
}
let mask = unsafe {
(XKBH.xkb_state_update_mask)(
self.xkb_state,
mods_depressed,
mods_latched,
mods_locked,
depressed_group,
latched_group,
locked_group,
)
};
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
// effective value of mods have changed, we need to update our state
self.mods_state.update_with(self.xkb_state);
}
}
pub fn get_one_sym_raw(&mut self, keycode: u32) -> u32 {
if !self.ready() {
return 0;
}
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) }
}
pub fn get_utf8_raw(&mut self, keycode: u32) -> Option<SmolStr> {
if !self.ready() {
return None;
}
let xkb_state = self.xkb_state;
self.make_string_with({
|ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(xkb_state, keycode, ptr, len) }
})
}
fn compose_feed_normal(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
self.compose_feed(self.xkb_compose_state, keysym)
}
fn compose_feed_2(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
self.compose_feed(self.xkb_compose_state_2, keysym)
}
fn compose_feed(
&mut self,
xkb_compose_state: *mut ffi::xkb_compose_state,
keysym: u32,
) -> Option<ffi::xkb_compose_feed_result> {
if !self.ready() || self.xkb_compose_state.is_null() {
return None;
}
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
unsafe { self.init_compose() };
}
Some(unsafe { (XKBCH.xkb_compose_state_feed)(xkb_compose_state, keysym) })
}
fn compose_status_normal(&mut self) -> Option<ffi::xkb_compose_status> {
self.compose_status(self.xkb_compose_state)
}
fn compose_status(
&mut self,
xkb_compose_state: *mut ffi::xkb_compose_state,
) -> Option<ffi::xkb_compose_status> {
if !self.ready() || xkb_compose_state.is_null() {
return None;
}
Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) })
}
fn compose_get_utf8_normal(&mut self) -> Option<SmolStr> {
self.compose_get_utf8(self.xkb_compose_state)
}
fn compose_get_utf8_2(&mut self) -> Option<SmolStr> {
self.compose_get_utf8(self.xkb_compose_state_2)
}
fn compose_get_utf8(
&mut self,
xkb_compose_state: *mut ffi::xkb_compose_state,
) -> Option<SmolStr> {
if !self.ready() || xkb_compose_state.is_null() {
return None;
}
self.make_string_with(|ptr, len| unsafe {
(XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len)
})
}
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
/// `xkb_state_key_get_utf8`.
fn make_string_with<F>(&mut self, mut f: F) -> Option<SmolStr>
where
F: FnMut(*mut c_char, usize) -> i32,
{
let size = f(ptr::null_mut(), 0);
if size == 0 {
return None;
}
let size = usize::try_from(size).unwrap();
self.scratch_buffer.clear();
// The allocated buffer must include space for the null-terminator
self.scratch_buffer.reserve(size + 1);
unsafe {
let written = f(
self.scratch_buffer.as_mut_ptr().cast(),
self.scratch_buffer.capacity(),
);
if usize::try_from(written).unwrap() != size {
// This will likely never happen
return None;
}
self.scratch_buffer.set_len(size);
};
byte_slice_to_smol_str(&self.scratch_buffer)
}
pub fn new() -> Result<Self, Error> {
if ffi::xkbcommon_option().is_none() {
return Err(Error::XKBNotFound);
}
let context =
unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
if context.is_null() {
return Err(Error::XKBNotFound);
}
let mut me = Self {
#[cfg(feature = "x11")]
xcb_connection: ptr::null_mut(),
xkb_context: context,
xkb_keymap: ptr::null_mut(),
xkb_state: ptr::null_mut(),
xkb_compose_table: ptr::null_mut(),
xkb_compose_state: ptr::null_mut(),
xkb_compose_state_2: ptr::null_mut(),
mods_state: ModifiersState::new(),
#[cfg(feature = "x11")]
core_keyboard_id: 0,
scratch_buffer: Vec::new(),
};
unsafe { me.init_compose() };
Ok(me)
}
#[cfg(feature = "x11")]
pub fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result<Self, Error> {
let mut me = Self::new()?;
me.xcb_connection = connection;
let result = unsafe {
(XKBXH.xkb_x11_setup_xkb_extension)(
connection,
1,
2,
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
assert_eq!(result, 1, "Failed to initialize libxkbcommon");
unsafe { me.init_with_x11_keymap() };
Ok(me)
}
unsafe fn init_compose(&mut self) {
let locale = env::var_os("LC_ALL")
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LC_CTYPE"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LANG"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.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,
);
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,
);
if compose_state.is_null() {
// init of compose state failed, continue without compose
(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,
);
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);
return;
}
self.xkb_compose_table = compose_table;
self.xkb_compose_state = compose_state;
self.xkb_compose_state_2 = compose_state_2;
}
unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) {
self.xkb_keymap = keymap;
self.xkb_state = state;
self.mods_state.update_with(state);
}
unsafe fn de_init(&mut self) {
(XKBH.xkb_state_unref)(self.xkb_state);
self.xkb_state = ptr::null_mut();
(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();
}
// 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,
);
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);
}
#[cfg(feature = "wayland")]
pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) {
if !self.xkb_keymap.is_null() {
self.de_init();
}
let map = 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,
);
if keymap.is_null() {
panic!("Received invalid keymap from compositor.");
}
let state = (XKBH.xkb_state_new)(keymap);
self.post_init(state, keymap);
}
#[cfg(feature = "wayland")]
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 }
}
#[inline]
pub fn ready(&self) -> bool {
!self.xkb_state.is_null()
}
#[inline]
pub fn mods_state(&self) -> ModifiersState {
self.mods_state
}
pub fn process_key_event(
&mut self,
keycode: u32,
state: ElementState,
repeat: bool,
) -> KeyEvent {
let mut event =
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
let physical_key = event.keycode();
let (logical_key, location) = event.key();
let text = event.text();
let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers();
let platform_specific = KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
};
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
platform_specific,
}
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
self.scratch_buffer.clear();
self.scratch_buffer.reserve(8);
loop {
let bytes_written = unsafe {
(XKBH.xkb_keysym_to_utf8)(
keysym,
self.scratch_buffer.as_mut_ptr().cast(),
self.scratch_buffer.capacity(),
)
};
if bytes_written == 0 {
return None;
} else if bytes_written == -1 {
self.scratch_buffer.reserve(8);
} else {
unsafe {
self.scratch_buffer
.set_len(bytes_written.try_into().unwrap())
};
break;
}
}
// Remove the null-terminator
self.scratch_buffer.pop();
byte_slice_to_smol_str(&self.scratch_buffer)
}
}
impl Drop for KbdState {
fn drop(&mut self) {
unsafe {
if !self.xkb_compose_state.is_null() {
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state);
}
if !self.xkb_compose_state_2.is_null() {
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state_2);
}
if !self.xkb_compose_table.is_null() {
(XKBCH.xkb_compose_table_unref)(self.xkb_compose_table);
}
if !self.xkb_state.is_null() {
(XKBH.xkb_state_unref)(self.xkb_state);
}
if !self.xkb_keymap.is_null() {
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
}
(XKBH.xkb_context_unref)(self.xkb_context);
}
}
}
struct KeyEventResults<'a> {
state: &'a mut KbdState,
keycode: u32,
keysym: u32,
compose: Option<XkbCompose>,
}
impl<'a> KeyEventResults<'a> {
fn new(state: &'a mut KbdState, keycode: u32, compose: bool) -> Self {
let keysym = state.get_one_sym_raw(keycode);
let compose = if compose {
Some(match state.compose_feed_normal(keysym) {
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => {
// Unwrapping is safe here, as `compose_feed` returns `None` when composition is uninitialized.
XkbCompose::Accepted(state.compose_status_normal().unwrap())
}
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored,
None => XkbCompose::Uninitialized,
})
} else {
None
};
KeyEventResults {
state,
keycode,
keysym,
compose,
}
}
fn keycode(&mut self) -> KeyCode {
keymap::raw_keycode_to_keycode(self.keycode)
}
pub fn key(&mut self) -> (Key, KeyLocation) {
self.keysym_to_key(self.keysym)
.unwrap_or_else(|(key, location)| match self.compose {
Some(XkbCompose::Accepted(ffi::xkb_compose_status::XKB_COMPOSE_COMPOSING)) => {
// When pressing a dead key twice, the non-combining variant of that character will be
// produced. Since this function only concerns itself with a single keypress, we simulate
// this double press here by feeding the keysym to the compose state twice.
self.state.compose_feed_2(self.keysym);
match self.state.compose_feed_2(self.keysym) {
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => (
// Extracting only a single `char` here *should* be fine, assuming that no dead
// key's non-combining variant ever occupies more than one `char`.
Key::Dead(
self.state
.compose_get_utf8_2()
.map(|s| s.chars().next().unwrap()),
),
location,
),
_ => (key, location),
}
}
_ => (
self.composed_text()
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
.map(Key::Character)
.unwrap_or(key),
location,
),
})
}
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
let mut keysyms = ptr::null();
let keysym_count = unsafe {
let layout = (XKBH.xkb_state_key_get_layout)(self.state.xkb_state, self.keycode);
(XKBH.xkb_keymap_key_get_syms_by_level)(
self.state.xkb_keymap,
self.keycode,
layout,
// NOTE: The level should be zero to ignore modifiers.
0,
&mut keysyms,
)
};
let keysym = if keysym_count == 1 {
unsafe { *keysyms }
} else {
0
};
self.keysym_to_key(keysym)
.unwrap_or_else(|(key, location)| {
(
self.state
.keysym_to_utf8_raw(keysym)
.map(Key::Character)
.unwrap_or(key),
location,
)
})
}
fn keysym_to_key(&mut self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = super::keymap::keysym_location(keysym);
let key = super::keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) {
Err((key, location))
} else {
Ok((key, location))
}
}
pub fn text(&mut self) -> Option<SmolStr> {
self.composed_text()
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
}
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the oppsite behaviour.
self.composed_text()
.unwrap_or_else(|_| self.state.get_utf8_raw(self.keycode))
}
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
if let Some(compose) = &self.compose {
match compose {
XkbCompose::Accepted(status) => match status {
ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => {
Ok(self.state.compose_get_utf8_normal())
}
ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
_ => Ok(None),
},
XkbCompose::Ignored | XkbCompose::Uninitialized => Err(()),
}
} else {
Err(())
}
}
}
/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
///
/// For some modifiers, this means that the key is currently pressed, others are toggled
/// (like caps lock).
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ModifiersState {
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "shift" key
pub shift: bool,
/// The "Caps lock" key
pub caps_lock: bool,
/// The "logo" key
///
/// Also known as the "windows" key on most keyboards
pub logo: bool,
/// The "Num lock" key
pub num_lock: bool,
}
impl ModifiersState {
fn new() -> Self {
Self::default()
}
fn update_with(&mut self, state: *mut ffi::xkb_state) {
let mod_name_is_active = |mod_name: &[u8]| unsafe {
(XKBH.xkb_state_mod_name_is_active)(
state,
mod_name.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
};
self.ctrl = mod_name_is_active(ffi::XKB_MOD_NAME_CTRL);
self.alt = mod_name_is_active(ffi::XKB_MOD_NAME_ALT);
self.shift = mod_name_is_active(ffi::XKB_MOD_NAME_SHIFT);
self.caps_lock = mod_name_is_active(ffi::XKB_MOD_NAME_CAPS);
self.logo = mod_name_is_active(ffi::XKB_MOD_NAME_LOGO);
self.num_lock = mod_name_is_active(ffi::XKB_MOD_NAME_NUM);
}
}
impl From<ModifiersState> for crate::keyboard::ModifiersState {
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
let mut to_mods = crate::keyboard::ModifiersState::empty();
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
to_mods
}
}
#[derive(Debug)]
pub enum Error {
/// libxkbcommon is not available
XKBNotFound,
}
#[derive(Copy, Clone, Debug)]
enum XkbCompose {
Accepted(ffi::xkb_compose_status),
Ignored,
Uninitialized,
}
// Note: This is track_caller so we can have more informative line numbers when logging
#[track_caller]
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
std::str::from_utf8(bytes)
.map(SmolStr::new)
.map_err(|e| {
warn!(
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
bytes
)
})
.ok()
}

View File

@@ -1,52 +1,52 @@
#![cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
#![cfg(free_unix)]
#[cfg(all(not(feature = "x11"), not(feature = "wayland")))]
#[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
use std::error::Error;
use std::{collections::VecDeque, env, fmt};
#[cfg(feature = "x11")]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
#[cfg(x11_platform)]
use std::{
ffi::CStr,
mem::MaybeUninit,
os::raw::*,
sync::{Arc, Mutex},
};
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
use once_cell::sync::Lazy;
#[cfg(feature = "x11")]
use parking_lot::Mutex;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use smol_str::SmolStr;
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub use self::x11::XNotSupported;
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
#[cfg(feature = "x11")]
use crate::platform::unix::XlibErrorHook;
#[cfg(feature = "wayland")]
use crate::window::Theme;
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::Event,
event_loop::{
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
},
event::{Event, KeyEvent},
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
keyboard::{Key, KeyCode},
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
},
};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
#[cfg(feature = "wayland")]
pub mod common;
#[cfg(wayland_platform)]
pub mod wayland;
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
@@ -60,9 +60,9 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
X,
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland,
}
@@ -87,79 +87,67 @@ impl ApplicationName {
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub name: Option<ApplicationName>,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub visual_infos: Option<XVisualInfo>,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub screen_id: Option<i32>,
#[cfg(feature = "x11")]
pub resize_increments: Option<Size>,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub base_size: Option<Size>,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub override_redirect: bool,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub x11_window_types: Vec<XWindowType>,
#[cfg(feature = "x11")]
pub gtk_theme_variant: Option<String>,
#[cfg(feature = "wayland")]
pub csd_theme: Option<Theme>,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
name: None,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
visual_infos: None,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
screen_id: None,
#[cfg(feature = "x11")]
resize_increments: None,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
base_size: None,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
override_redirect: false,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
x11_window_types: vec![XWindowType::Normal],
#[cfg(feature = "x11")]
gtk_theme_variant: None,
#[cfg(feature = "wayland")]
csd_theme: None,
}
}
}
#[cfg(feature = "x11")]
pub static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
#[cfg(x11_platform)]
pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
#[derive(Debug, Clone)]
pub enum OsError {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
XError(XError),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
XMisc(&'static str),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
WaylandMisc(&'static str),
}
impl fmt::Display for OsError {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
OsError::XMisc(e) => _f.pad(e),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
OsError::WaylandMisc(e) => _f.pad(e),
}
}
}
pub enum Window {
#[cfg(feature = "x11")]
pub(crate) enum Window {
#[cfg(x11_platform)]
X(x11::Window),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland(wayland::Window),
}
@@ -186,26 +174,26 @@ impl WindowId {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
X(x11::DeviceId),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(x11::DeviceId::dummy());
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
X(x11::MonitorHandle),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland(wayland::MonitorHandle),
}
@@ -221,17 +209,17 @@ pub enum MonitorHandle {
macro_rules! x11_or_wayland {
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => {
match $what {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
$enum::X($($c1)*) => $enum2::X($x),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
$enum::Wayland($($c1)*) => $enum2::Wayland($x),
}
};
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => {
match $what {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
$enum::X($($c1)*) => $x,
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
$enum::Wayland($($c1)*) => $x,
}
};
@@ -265,20 +253,20 @@ impl MonitorHandle {
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64)
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _)
}
#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoMode {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
X(x11::VideoMode),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland(wayland::VideoMode),
}
@@ -299,7 +287,7 @@ impl VideoMode {
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
pub fn monitor(&self) -> MonitorHandle {
x11_or_wayland!(match self; VideoMode(m) => m.monitor())
}
}
@@ -312,11 +300,11 @@ impl Window {
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
match *window_target {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
}
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref window_target) => {
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
}
@@ -326,9 +314,9 @@ impl Window {
#[inline]
pub fn id(&self) -> WindowId {
match self {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Self::Wayland(window) => window.id(),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
Self::X(window) => window.id(),
}
}
@@ -338,6 +326,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_title(title));
}
#[inline]
pub fn set_transparent(&self, transparent: bool) {
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
}
#[inline]
pub fn set_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
@@ -388,6 +381,16 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions))
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.resize_increments())
}
#[inline]
pub fn set_resize_increments(&self, increments: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments))
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
@@ -398,6 +401,16 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.is_resizable())
}
#[inline]
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons))
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
x11_or_wayland!(match self; Window(w) => w.enabled_buttons())
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
@@ -418,6 +431,11 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.drag_window())
}
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
@@ -425,7 +443,7 @@ impl Window {
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
x11_or_wayland!(match self; Window(w) => w.scale_factor())
}
#[inline]
@@ -449,12 +467,17 @@ impl Window {
}
#[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> {
pub fn is_minimized(&self) -> Option<bool> {
x11_or_wayland!(match self; Window(w) => w.is_minimized())
}
#[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
x11_or_wayland!(match self; Window(w) => w.fullscreen())
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor))
}
@@ -469,11 +492,11 @@ impl Window {
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
pub fn set_window_level(&self, _level: WindowLevel) {
match self {
#[cfg(feature = "x11")]
Window::X(ref w) => w.set_always_on_top(_always_on_top),
#[cfg(feature = "wayland")]
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_level(_level),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
}
@@ -481,16 +504,21 @@ impl Window {
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
match self {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
}
#[inline]
pub fn set_ime_position(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size))
}
#[inline]
pub fn reset_dead_keys(&self) {
common::xkb_state::reset_dead_keys()
}
#[inline]
@@ -498,20 +526,25 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
}
#[inline]
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose))
}
#[inline]
pub fn focus_window(&self) {
match self {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
Window::X(ref w) => w.focus_window(),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
match self {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
}
@@ -522,21 +555,17 @@ impl Window {
}
#[inline]
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
pub fn current_monitor(&self) -> Option<MonitorHandle> {
match self {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(RootMonitorHandle {
inner: current_monitor,
})
Some(current_monitor)
}
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(RootMonitorHandle {
inner: current_monitor,
})
Some(current_monitor)
}
}
}
@@ -544,13 +573,13 @@ impl Window {
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
Window::X(ref window) => window
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Window::Wayland(ref window) => window
.available_monitors()
.into_iter()
@@ -560,16 +589,14 @@ impl Window {
}
#[inline]
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
match self {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
Window::X(ref window) => {
let primary_monitor = MonitorHandle::X(window.primary_monitor());
Some(RootMonitorHandle {
inner: primary_monitor,
})
Some(primary_monitor)
}
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Window::Wayland(ref window) => window.primary_monitor(),
}
}
@@ -583,23 +610,73 @@ impl Window {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
}
#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
x11_or_wayland!(match self; Window(window) => window.set_theme(theme))
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
x11_or_wayland!(match self; Window(window) => window.theme())
}
#[inline]
pub fn has_focus(&self) -> bool {
x11_or_wayland!(match self; Window(window) => window.has_focus())
}
pub fn title(&self) -> String {
x11_or_wayland!(match self; Window(window) => window.title())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub key_without_modifiers: Key,
pub text_with_all_modifiers: Option<SmolStr>,
}
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifiers
.as_ref()
.map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}
impl KeyCodeExtScancode for KeyCode {
fn from_scancode(scancode: u32) -> KeyCode {
common::keymap::scancode_to_keycode(scancode)
}
fn to_scancode(self) -> Option<u32> {
common::keymap::keycode_to_scancode(self)
}
}
/// Hooks for X11 errors.
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
pub(crate) static mut XLIB_ERROR_HOOKS: Lazy<Mutex<Vec<XlibErrorHook>>> =
Lazy::new(|| Mutex::new(Vec::new()));
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock();
let xconn_lock = X11_BACKEND.lock().unwrap();
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().iter() {
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
@@ -624,25 +701,25 @@ unsafe extern "C" fn x_error_callback(
// Don't log error.
if !error_handled {
error!("X11 error: {:#?}", error);
// XXX only update the error, if it wasn't handled by any of the hooks.
*xconn.latest_error.lock().unwrap() = Some(error);
}
*xconn.latest_error.lock() = Some(error);
}
// Fun fact: this return value is completely ignored.
0
}
pub enum EventLoop<T: 'static> {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop<T>>),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
X(x11::EventLoop<T>),
}
pub enum EventLoopProxy<T: 'static> {
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
X(x11::EventLoopProxy<T>),
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland(wayland::EventLoopProxy<T>),
}
@@ -663,13 +740,13 @@ impl<T: 'static> EventLoop<T> {
);
}
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
if attributes.forced_backend == Some(Backend::X) {
// TODO: Propagate
return EventLoop::new_x11_any_thread().unwrap();
}
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
if attributes.forced_backend == Some(Backend::Wayland) {
// TODO: Propagate
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
@@ -679,57 +756,55 @@ impl<T: 'static> EventLoop<T> {
match env_var.as_str() {
"x11" => {
// TODO: propagate
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
return EventLoop::new_x11_any_thread()
.expect("Failed to initialize X11 backend");
#[cfg(not(feature = "x11"))]
#[cfg(not(x11_platform))]
panic!("x11 feature is not enabled")
}
"wayland" => {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
return EventLoop::new_wayland_any_thread()
.expect("Failed to initialize Wayland backend");
#[cfg(not(feature = "wayland"))]
#[cfg(not(wayland_platform))]
panic!("wayland feature is not enabled");
}
_ => panic!(
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
BACKEND_PREFERENCE_ENV_VAR,
"Unknown environment variable value for {BACKEND_PREFERENCE_ENV_VAR}, try one of `x11`,`wayland`",
),
}
}
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
let wayland_err = match EventLoop::new_wayland_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
let x11_err = match EventLoop::new_x11_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(not(feature = "wayland"))]
#[cfg(not(wayland_platform))]
let wayland_err = "backend disabled";
#[cfg(not(feature = "x11"))]
#[cfg(not(x11_platform))]
let x11_err = "backend disabled";
panic!(
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
wayland_err, x11_err,
"Failed to initialize any backend! Wayland status: {wayland_err:?} X11 status: {x11_err:?}",
);
}
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().as_ref() {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(err.clone()),
};
@@ -767,9 +842,9 @@ impl<T: 'static> EventLoopProxy<T> {
}
pub enum EventLoopWindowTarget<T> {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
Wayland(wayland::EventLoopWindowTarget<T>),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
X(x11::EventLoopWindowTarget<T>),
}
@@ -777,9 +852,9 @@ impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(_) => true,
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
_ => false,
}
}
@@ -787,13 +862,13 @@ impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match *self {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref evlp) => evlp
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp
.x_connection()
.available_monitors()
@@ -804,27 +879,25 @@ impl<T> EventLoopWindowTarget<T> {
}
#[inline]
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
match *self {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => {
let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
Some(RootMonitorHandle {
inner: primary_monitor,
})
Some(primary_monitor)
}
}
}
#[inline]
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
match *self {
#[cfg(feature = "wayland")]
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(_) => (),
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp.set_listen_device_events(_allowed),
}
}

View File

@@ -1,166 +0,0 @@
//! SCTK environment setup.
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
use sctk::reexports::client::protocol::wl_shell::WlShell;
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
use sctk::reexports::client::{Attached, DispatchData};
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::{Environment, SimpleGlobal};
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
use sctk::shell::{Shell, ShellHandler, ShellHandling};
use sctk::shm::ShmHandler;
/// Set of extra features that are supported by the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
pointer_constraints: bool,
xdg_activation: bool,
}
impl WindowingFeatures {
/// Create `WindowingFeatures` based on the presented interfaces.
pub fn new(env: &Environment<WinitEnv>) -> Self {
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
pointer_constraints,
xdg_activation,
}
}
pub fn pointer_constraints(&self) -> bool {
self.pointer_constraints
}
pub fn xdg_activation(&self) -> bool {
self.xdg_activation
}
}
sctk::environment!(WinitEnv,
singles = [
WlShm => shm,
WlCompositor => compositor,
WlSubcompositor => subcompositor,
WlShell => shell,
XdgWmBase => shell,
ZxdgShellV6 => shell,
ZxdgDecorationManagerV1 => decoration_manager,
ZwpRelativePointerManagerV1 => relative_pointer_manager,
ZwpPointerConstraintsV1 => pointer_constraints,
ZwpTextInputManagerV3 => text_input_manager,
XdgActivationV1 => xdg_activation,
],
multis = [
WlSeat => seats,
WlOutput => outputs,
]
);
/// The environment that we utilize.
pub struct WinitEnv {
seats: SeatHandler,
outputs: OutputHandler,
shm: ShmHandler,
compositor: SimpleGlobal<WlCompositor>,
subcompositor: SimpleGlobal<WlSubcompositor>,
shell: ShellHandler,
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
xdg_activation: SimpleGlobal<XdgActivationV1>,
}
impl WinitEnv {
pub fn new() -> Self {
// Output tracking for available_monitors, etc.
let outputs = OutputHandler::new();
// Keyboard/Pointer/Touch input.
let seats = SeatHandler::new();
// Essential globals.
let shm = ShmHandler::new();
let compositor = SimpleGlobal::new();
let subcompositor = SimpleGlobal::new();
// Gracefully handle shell picking, since SCTK automatically supports multiple
// backends.
let shell = ShellHandler::new();
// Server side decorations.
let decoration_manager = SimpleGlobal::new();
// Device events for pointer.
let relative_pointer_manager = SimpleGlobal::new();
// Pointer grab functionality.
let pointer_constraints = SimpleGlobal::new();
// IME handling.
let text_input_manager = SimpleGlobal::new();
// Surface activation.
let xdg_activation = SimpleGlobal::new();
Self {
seats,
outputs,
shm,
compositor,
subcompositor,
shell,
decoration_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
xdg_activation,
}
}
}
impl ShellHandling for WinitEnv {
fn get_shell(&self) -> Option<Shell> {
self.shell.get_shell()
}
}
impl SeatHandling for WinitEnv {
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> SeatListener {
self.seats.listen(f)
}
}
impl OutputHandling for WinitEnv {
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
&mut self,
f: F,
) -> OutputStatusListener {
self.outputs.listen(f)
}
}

View File

@@ -1,158 +1,93 @@
//! The event-loop routines.
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::process;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
use sctk::environment::Environment;
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
use sctk::WaylandSource;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use super::env::{WindowingFeatures, WinitEnv};
use super::output::OutputManager;
use super::seat::SeatManager;
use super::window::shim::{self, WindowCompositorUpdate, WindowUserRequest};
use super::{DeviceId, WindowId};
mod proxy;
mod sink;
mod state;
pub mod sink;
pub use proxy::EventLoopProxy;
pub use sink::EventSink;
pub use state::WinitState;
use sink::EventSink;
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
use super::state::{WindowCompositorUpdate, WinitState};
use super::{DeviceId, WindowId};
pub struct EventLoopWindowTarget<T> {
/// Wayland display.
pub display: Display,
/// Environment to handle object creation, etc.
pub env: Environment<WinitEnv>,
/// Event loop handle.
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
/// Output manager.
pub output_manager: OutputManager,
/// State that we share across callbacks.
pub state: RefCell<WinitState>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// A proxy to wake up event loop.
pub event_loop_awakener: calloop::ping::Ping,
/// The available windowing features.
pub windowing_features: WindowingFeatures,
/// Theme manager to manage cursors.
///
/// It's being shared between all windows to avoid loading
/// multiple similar themes.
pub theme_manager: ThemeManager,
_marker: std::marker::PhantomData<T>,
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.display.get_display_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
}
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop.
pub struct EventLoop<T: 'static> {
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// Event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
/// Wayland display.
display: Display,
/// Pending user events.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
/// Window target.
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
// we don't really want, since it'll break public API by a lot.
/// Pending events from the user.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// The Wayland dispatcher to has raw access to the queue when needed, such as
/// when creating a new window.
wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
connection: Connection,
/// Event loop window target.
window_target: RootEventLoopWindowTarget<T>,
/// Output manager.
_seat_manager: SeatManager,
// XXX drop after everything else, just to be safe.
/// Calloop's event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
// Connect to wayland server and setup event queue.
let display = Display::connect_to_env()?;
let mut event_queue = display.create_event_queue();
let display_proxy = display.attach(event_queue.token());
let connection = Connection::connect_to_env()?;
// Setup environment.
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
let (globals, mut event_queue) = globals::registry_queue_init(&connection)?;
let queue_handle = event_queue.handle();
// Create event loop.
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
// Build windowing features.
let windowing_features = WindowingFeatures::new(&env);
let event_loop = calloop::EventLoop::<WinitState>::try_new()?;
// Create a theme manager.
let compositor = env.require_global::<WlCompositor>();
let shm = env.require_global::<WlShm>();
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
// Setup theme seat and output managers.
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
let output_manager = OutputManager::new(&env);
// NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server.
event_queue.roundtrip(&mut winit_state)?;
// A source of events that we plug into our event loop.
let wayland_source = WaylandSource::new(event_queue);
// Register Wayland source.
let wayland_source = WaylandSource::new(event_queue)?;
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state, |event, object, _| {
panic!(
"[calloop] Encountered an orphan event: {}@{} : {}",
event.interface,
object.as_ref().id(),
event.name
);
})
queue.dispatch_pending(winit_state)
});
let _wayland_source_dispatcher = event_loop
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
// A source of user events.
// Setup the user proxy.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
// User events channel.
event_loop
.handle()
.insert_source(user_events_channel, move |event, _, _| {
@@ -163,52 +98,30 @@ impl<T: 'static> EventLoop<T> {
// An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
// Handler of window requests.
event_loop
.handle()
.insert_source(event_loop_awakener_source, move |_, _, state| {
// Drain events here as well to account for application doing batch event processing
// on RedrawEventsCleared.
shim::handle_window_requests(state);
.insert_source(event_loop_awakener_source, move |_, _, _| {
// No extra handling is required, we just need to wake-up.
})?;
let event_loop_handle = event_loop.handle();
let window_map = HashMap::new();
let event_sink = EventSink::new();
let window_user_requests = HashMap::new();
let window_compositor_updates = HashMap::new();
// Create event loop window target.
let event_loop_window_target = EventLoopWindowTarget {
display: display.clone(),
env,
state: RefCell::new(WinitState {
window_map,
event_sink,
window_user_requests,
window_compositor_updates,
}),
event_loop_handle,
output_manager,
event_loop_awakener,
let window_target = EventLoopWindowTarget {
connection: connection.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
windowing_features,
theme_manager,
_marker: std::marker::PhantomData,
event_loop_awakener,
queue_handle,
state: RefCell::new(winit_state),
_marker: PhantomData,
};
// Create event loop itself.
let event_loop = Self {
event_loop,
display,
pending_user_events,
connection,
wayland_dispatcher,
_seat_manager: seat_manager,
user_events_sender,
pending_user_events,
event_loop,
window_target: RootEventLoopWindowTarget {
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
_marker: std::marker::PhantomData,
p: PlatformEventLoopWindowTarget::Wayland(window_target),
_marker: PhantomData,
},
};
@@ -228,7 +141,11 @@ impl<T: 'static> EventLoop<T> {
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::Poll;
let pending_user_events = self.pending_user_events.clone();
// XXX preallocate certian structures to avoid allocating on each loop iteration.
let mut window_ids = Vec::<WindowId>::new();
let mut compositor_updates = Vec::<WindowCompositorUpdate>::new();
let mut buffer_sink = EventSink::new();
callback(
Event::NewEvents(StartCause::Init),
@@ -236,23 +153,19 @@ impl<T: 'static> EventLoop<T> {
&mut control_flow,
);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// XXX For consistency all platforms must emit a 'Resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
callback(Event::Resumed, &self.window_target, &mut control_flow);
let mut window_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new();
let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = Vec::new();
let mut event_sink_back_buffer = Vec::new();
// NOTE We break on errors from dispatches, since if we've got protocol error
// XXX We break on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// 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 = loop {
// Send pending events to the server.
let _ = self.display.flush();
// Flush the connection.
let _ = self.connection.flush();
// 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
@@ -268,13 +181,16 @@ impl<T: 'static> EventLoop<T> {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
}
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
_ => unreachable!(),
};
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
match queue.dispatch_pending(state) {
Ok(dispatched) => dispatched > 0,
Err(error) => break error.raw_os_error().unwrap_or(1),
Err(error) => {
error!("Error dispatching wayland queue: {}", error);
break 1;
}
}
};
@@ -282,7 +198,7 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::from_millis(0);
let timeout = Duration::ZERO;
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
}
@@ -295,7 +211,7 @@ impl<T: 'static> EventLoop<T> {
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::from_millis(0))
Some(Duration::ZERO)
} else {
None
};
@@ -320,7 +236,7 @@ impl<T: 'static> EventLoop<T> {
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::from_millis(0)
Duration::ZERO
};
if let Err(error) = self.loop_dispatch(Some(duration)) {
@@ -353,7 +269,7 @@ impl<T: 'static> EventLoop<T> {
// 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 pending_user_events.borrow_mut().drain(..) {
for user_event in self.pending_user_events.borrow_mut().drain(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
&self.window_target,
@@ -362,34 +278,30 @@ impl<T: 'static> EventLoop<T> {
);
}
// Process 'new' pending updates from compositor.
// Drain the pending compositor updates.
self.with_state(|state| {
window_compositor_updates.clear();
window_compositor_updates.extend(
state
.window_compositor_updates
.iter_mut()
.map(|(wid, window_update)| (*wid, mem::take(window_update))),
);
compositor_updates.append(&mut state.window_compositor_updates)
});
for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() {
if let Some(scale_factor) = window_compositor_update.scale_factor.map(|f| f as f64)
{
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if let Some(scale_factor) = compositor_update.scale_factor {
let mut physical_size = self.with_state(|state| {
let window_handle = state.window_map.get(window_id).unwrap();
let mut size = window_handle.size.lock().unwrap();
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
// Update the new logical size if it was changed.
let window_size = window_compositor_update.size.unwrap_or(*size);
*size = window_size;
window_size.to_physical(scale_factor)
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
@@ -400,65 +312,58 @@ impl<T: 'static> EventLoop<T> {
&mut callback,
);
// We don't update size on a window handle since we'll do that later
// when handling size update.
let new_logical_size = physical_size.to_logical(scale_factor);
window_compositor_update.size = Some(new_logical_size);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.resize(new_logical_size);
});
}
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
}
if let Some(size) = window_compositor_update.size.take() {
if let Some(size) = compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
let mut window_size = window_handle.size.lock().unwrap();
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
// Always issue resize event on scale factor change.
let physical_size = if window_compositor_update.scale_factor.is_none()
&& *window_size == size
{
// The size hasn't changed, don't inform downstream about that.
None
} else {
*window_size = size;
let scale_factor =
sctk::get_surface_scale_factor(window_handle.window.surface());
let physical_size = size.to_physical(scale_factor as f64);
Some(physical_size)
};
let scale_factor = window.scale_factor();
let physical_size = logical_to_physical_rounded(size, scale_factor);
// We still perform all of those resize related logic even if the size
// hasn't changed, since GNOME relies on `set_geometry` calls after
// configures.
window_handle.window.resize(size.width, size.height);
window_handle.window.refresh();
// TODO could probably bring back size reporting optimization.
// Mark that refresh isn't required, since we've done it right now.
// Mark the window as needed a redraw.
state
.window_user_requests
.get_mut(window_id)
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.refresh_frame = false;
.redraw_requested
.store(true, Ordering::Relaxed);
physical_size
});
if let Some(physical_size) = physical_size {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// If the close is requested, send it here.
if window_compositor_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
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(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
@@ -468,18 +373,20 @@ impl<T: 'static> EventLoop<T> {
}
}
// The purpose of the back buffer and that swap is to not hold borrow_mut when
// we're doing callback to the user, since we can double borrow if the user decides
// to create a window in one of those callbacks.
// Push the events directly from the window.
self.with_state(|state| {
std::mem::swap(
&mut event_sink_back_buffer,
&mut state.event_sink.window_events,
)
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
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);
}
// Handle pending window events.
for event in event_sink_back_buffer.drain(..) {
// Handle non-synthetic events.
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
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);
}
@@ -492,42 +399,41 @@ impl<T: 'static> EventLoop<T> {
&mut callback,
);
// Apply user requests, so every event required resize and latter surface commit will
// be applied right before drawing. This will also ensure that every `RedrawRequested`
// event will be delivered in time.
// Collect the window ids
self.with_state(|state| {
shim::handle_window_requests(state);
window_ids.extend(state.window_requests.get_mut().keys());
});
// Process 'new' pending updates from compositor.
self.with_state(|state| {
window_user_requests.clear();
window_user_requests.extend(
state
.window_user_requests
.iter_mut()
.map(|(wid, window_request)| (*wid, mem::take(window_request))),
);
});
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
false
} else {
let mut redraw_requested = window_requests
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Handle RedrawRequested events.
for (window_id, mut window_request) in window_user_requests.iter() {
// Handle refresh of the frame.
if window_request.refresh_frame {
self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
window_handle.window.refresh();
});
// Redraw the frames while at it.
redraw_requested |= state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap()
.refresh_frame();
// In general refreshing the frame requires surface commit, those force user
// to redraw.
window_request.redraw_requested = true;
}
redraw_requested
}
});
// Handle redraw request.
if window_request.redraw_requested {
if request_redraw {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(*window_id)),
Event::RedrawRequested(crate::window::WindowId(window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
@@ -558,14 +464,14 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
#[cfg(x11_platform)]
_ => unreachable!(),
};
f(state)
callback(state)
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
@@ -575,8 +481,44 @@ impl<T: 'static> EventLoop<T> {
_ => unreachable!(),
};
self.event_loop
.dispatch(timeout, state)
.map_err(|error| error.into())
self.event_loop.dispatch(timeout, state).map_err(|error| {
error!("Error dispatching event loop: {}", error);
error.into()
})
}
}
pub struct EventLoopWindowTarget<T> {
/// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping,
/// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>,
// TODO remove that RefCell once we can pass `&mut` in `Window::new`.
/// Winit state.
pub state: RefCell<WinitState>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
pub connection: Connection,
_marker: std::marker::PhantomData<T>,
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
}
// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -1,5 +1,7 @@
//! An event loop's sink to deliver events from the Wayland event callbacks.
use std::vec::Drain;
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
use crate::window::WindowId as RootWindowId;
@@ -19,6 +21,7 @@ impl EventSink {
}
/// Add new device event to a queue.
#[inline]
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
self.window_events.push(Event::DeviceEvent {
event,
@@ -27,10 +30,21 @@ impl EventSink {
}
/// Add new window event to a queue.
#[inline]
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(window_id),
});
}
#[inline]
pub fn append(&mut self, other: &mut Self) {
self.window_events.append(&mut other.window_events);
}
#[inline]
pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> {
self.window_events.drain(..)
}
}

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