Compare commits

..

127 Commits

Author SHA1 Message Date
Kirill Chibisov
c53a574bff Release 0.27.1 version 2022-07-30 19:33:23 +03:00
Kirill Chibisov
95246d81c1 On X11, fix crash when can't disable IME
Fixes #2402.
2022-07-30 17:15:57 +03:00
Kirill Chibisov
bf537009d9 Explicitly specify minimum supported rust version
This should help with distributing apps using winit.

Fixes #1075.
2022-07-29 14:39:41 +03:00
Kirill Chibisov
50035643f7 Release 0.27.0 version 2022-07-26 23:54:12 +03:00
Mads Marquart
64c22f9075 Fix changelog entry wrt scrolling
The breaking change was put into the wrong release section.
2022-07-26 22:25:00 +03:00
Marijn Suijten
4895a29e92 ci: manually point ANDROID_NDK_ROOT to latest supplied version
It seems the symlink to `ndk-bundle` and this environment variable
pointing to it have been removed to prevent the sdkmanager from failing,
when finding the SDK setup to be in an "indeterminate" state.  It is now
up to the users themselves to install an NDK through that tool or point
the right variables to a preinstalled "latest" NDK.

https://github.com/actions/virtual-environments/issues/2689
https://github.com/actions/virtual-environments/pull/5926
2022-07-26 19:55:06 +03:00
Robert Bragg
6cdb3179c8 Consistently deliver a Resumed event on all platforms
To be more consistent with mobile platforms this updates the Windows,
macOS, Wayland, X11 and Web backends to all emit a Resumed event
immediately after the initial `NewEvents(StartCause::Init)` event.

The documentation for Suspended and Resumed has also been updated
to provide general recommendations for how to handle Suspended and
Resumed events in portable applications as well as providing
Android and iOS specific details.

This consistency makes it possible to write applications that lazily
initialize their graphics state when the application resumes without
any platform-specific knowledge. Previously, applications that wanted
to run on Android and other systems would have to maintain two,
mutually-exclusive, initialization paths.

Note: This patch does nothing to guarantee that Suspended events will
be delivered. It's still reasonable to say that most OSs without a
formal lifecycle for applications will simply never "suspend" your
application. There are currently no known portability issues caused
by not delivering `Suspended` events consistently and technically
it's not possible to guarantee the delivery of `Suspended` events if
the OS doesn't define an application lifecycle. (app can always be
terminated without any kind of clean up notification on most
non-mobile OSs)

Fixes #2185.

Co-authored-by: Marijn Suijten <marijns95@gmail.com>
Co-authored-by: Markus Røyset <maroider@protonmail.com>
2022-07-26 16:03:12 +03:00
Kirill Chibisov
4fd52af682 Fix type hint reference for xlib hook 2022-07-26 16:02:09 +03:00
Marijn Suijten
5a0bad130d Bump ndk and ndk-glue dependencies to stable 0.7.0 release (#2392) 2022-07-25 15:20:31 +02:00
Amr Bashir
08d025968e Fix hiding a maximized window On Windows (#2336) 2022-07-23 14:23:58 +02:00
Amr Bashir
1cd0e94c26 Windows: apply skip taskbar state when taskbar is restarted (#2380) 2022-07-22 19:33:22 +02:00
Kirill Chibisov
1ec976f95e Add method to hook xlib error handler
This should help glutin to handle errors coming from GLX
and offer multithreading support in a safe way.

Fixes #2378.
2022-07-22 20:21:28 +03:00
Kirill Chibisov
f10ef5f331 On macOS, fix confirmed character inserted
When confirming input in e.g. Korean IME or using characters like
`+` winit was sending those twice, once via `Ime::Commit` and the
other one via `ReceivedCharacter`, since those events weren't generating
any `Ime::Preedit` and were forwarded due to `do_command_by_selector`.
2022-07-21 22:23:22 +03:00
Kirill Chibisov
653bc59813 Update raw-window-handle to v0.5.0
This updates raw-window-handle to v0.5.0.
2022-07-21 22:22:36 +03:00
Rodrigo Batista de Moraes
3e991e13dc Android: avoid deadlocks while handling UserEvent (#2343)
Replace `Arc<Mutex<VecDeque<T>>` by `mpsc`
2022-07-20 19:52:36 +02:00
Markus Røyset
5397b53e04 Tidy up "platform-specifc" doc sections (#2356)
* Tidy up "platform-specific" doc sections

* Unrelated grammatical fix

* Subjective improvements
2022-07-20 13:45:12 +02:00
Kirill Chibisov
f09259f6de Bump sctk-adwaita to 0.4.1
This should force the use of system libraries for Fontconfig
and freetype instead of building them with cmake if missing.

This also fixes compilation failures on nightly.

Fixes #2373.
2022-07-20 11:50:49 +03:00
Josh Groves
430a49ebc2 Fix typos (#2375) 2022-07-15 18:32:12 +02:00
Steve Wooster
1091a8ba1a Make winit focus take activity into account on Windows (#2159)
winit's notion of "focus" is very simple; you're either focused or not.
However, Windows has both notions of focused window and active window
and paying attention only to WM_SETFOCUS/WM_KILLFOCUS can cause a window
to believe the user is interacting with it when they're not. (this
manifests when a user switches to another application between when a
winit application starts and it creates its first window)
2022-07-15 10:27:27 +02:00
Josh Groves
9116b6c8cd windows: Use correct value for mouse wheel delta (#2374) 2022-07-14 22:00:22 +02:00
Josh Groves
990e34a129 web: add with_prevent_default, with_focusable (#2365)
* web: add `with_prevent_default`, `with_focusable`

`with_prevent_default` controls whether `event.preventDefault` is called

`with_focusable` controls whether `tabindex` is added

Fixes #1768

* Remove extra space from CHANGELOG
2022-07-14 17:52:31 +02:00
Marijn Suijten
472d7b9376 android: Hold NativeWindow lock until after notifying the user with Event::Suspended (#2307)
This applies https://github.com/rust-windowing/android-ndk-rs/issues/117
on the `winit` side: Android destroys its window/surface as soon as the
user returns from [`onNativeWindowDestroyed`], and we "fixed" this on
the `ndk-glue` side by sending the `WindowDestroyed` event before
locking the window and removing it: this lock has to wait for any user
of `ndk-glue` - ie. `winit` - to give up its readlock on the window,
which is what we utilize here to give users of `winit` "time" to destroy
any resource created on top of a `RawWindowHandle`.

since we can't pass the user a `RawWindowHandle` through the
`HasRawWindowHandle` trait we have to document this case explicitly and
keep the lock alive on the `winit` side instead.

[`onNativeWindowDestroyed`]: https://developer.android.com/ndk/reference/struct/a-native-activity-callbacks#onnativewindowdestroyed
2022-07-14 12:35:49 +02:00
Markus Røyset
50dd7881b1 Fix changelog entry for EventLoopExtWebSys (#2372) 2022-07-13 17:54:06 +02:00
Liam Murphy
aa8f8db305 web: Add EventLoop::spawn (#2208)
* web: Add `EventLoop::spawn`

This is the same as `EventLoop::run`, but doesn't throw an exception in order to return `!`.

I decided to name it `spawn` rather than `run_web` because I think that's more descriptive, but I'm happy to change it to `run_web`.

Resolves #1714

* Update src/platform/web.rs

Co-authored-by: Markus Røyset <maroider@protonmail.com>

* Fix outdated names

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2022-07-13 17:17:18 +02:00
Lucas Kent
cdd9b1e1eb web: Manually emit focused event on mouse click (#2202)
* Manually emit focused event on mouse click

* Update CHANGELOG.md

Co-authored-by: Markus Røyset <maroider@protonmail.com>

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2022-07-13 00:46:15 +02:00
Kirill Chibisov
2d2ce70edc On Wayland, drop wl_surface on window close 2022-07-09 21:41:18 +03:00
Kirill Chibisov
78f1d1df38 On Wayland send Focused(false) for new window
On Wayland winit will always get an explicit focused event from the
system and will transfer it downstream. So send focused false to enforce
it.
2022-07-09 18:17:41 +03:00
Kirill Chibisov
a06bb3f992 Add refresh_rate_millihertz for MonitorHandle
This also alters `VideoMode::refresh_rate` to
`VideoMode::refresh_rate_millihertz` which now returns monitor refresh rate in
mHz.
2022-07-08 13:25:56 +03:00
trimental
e289f30e5d Add 'WindowEvent::Occluded(bool)'
This commits and an event to track window occlusion state,
which could help optimize rendering downstream.
2022-07-06 21:46:25 +03:00
Shinichi Tanaka
4b10993970 Fix infinite recursion in WindowId conversion methods 2022-07-05 20:09:40 +03:00
Diggory Hardy
d78a870e66 examples/multiwindow.rs: ignore synthetic key press events 2022-07-03 22:25:08 +03:00
Kirill Chibisov
cb41c58f21 Implement From<u64> for WindowId and vise-versa
This should help downstream applications to expose WindowId to the end
users via e.g. IPC to control particular windows in multi window
systems.
2022-07-02 14:27:19 +03:00
Diggory Hardy
c55d97183d Less redundancy and improve fullscreen in examples
Remove examples/minimize which is redundant
2022-07-01 14:07:10 +03:00
Aaron Hill
8646cbc9f5 Map XK_Caps_Lock to VirtualKeyCode::Capital (#1864)
This allows applications to handle events for the caps lock key under X11
2022-06-23 17:59:04 +02:00
Amr Bashir
64c1d4c5bb Fix conflict in WindowFlags on Windows 2022-06-22 20:44:00 +03:00
MarcusGrass
76b949c196 Disallow multiple EventLoop creation 2022-06-22 20:43:25 +03:00
Kirill Chibisov
c93ef47b9b Bump smithay-client-toolkit to v0.16.0 2022-06-20 10:19:49 +03:00
Marijn Suijten
2b414cd825 ci: Disallow warnings in rustdoc and test private items (#2341)
Make sure `cargo doc` runs cleanly without any warnings in the CI - some
recently introduced but still allowing a PR to get merged.

In case someone wishes to add docs on private items, make sure those
adhere to the same standards.
2022-06-17 14:19:09 +02:00
Nazarí González
ac42447459 macOS: disallow_highdpi will set explicity the value to avoid the SO value by default (#2339)
Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-06-17 01:12:05 +02:00
Markus Røyset
401d20fa1f Fix doubled device events on X11
Fixes #2332
2022-06-13 19:24:56 +03:00
Marijn Suijten
6b5b570b45 examples/window_run_return: Enable on Android (#2321)
Android also supports `EventLoopExtRunReturn`.  The user will still have
to follow the README to turn this example into a `cdylib` and add the
`ndk_glue::main()` initialization attribute, though.
2022-06-13 16:40:21 +02:00
Kirill Chibisov
9e6f666616 Refine Window::set_cursor_grab API
This commit renames `Window::set_cursor_grab` to
`Window::set_cursor_grab_mode`. The new API now accepts enumeration
to control the way cursor grab is performed. The value could be: `lock`,
`confine`, or `none`.

This commit also implements `Window::set_cursor_position` for Wayland,
since it's tied to locked cursor.

Implements API from #1677.
2022-06-13 09:43:14 +03:00
Kirill Chibisov
8ef9fe44c7 Add WindowBuilder::transparent
This is required to help hardware accelerated libraries like glutin
that accept WindowBuilder instead of RawWindowHandle, since the api
to access builder properties directly was removed.

Follow up to 44288f6.
2022-06-12 09:53:28 +03:00
Mads Marquart
3e0a544eb8 Documentation cleanup (#2328)
* Remove redundant documentation links

* Add note to README about windows not showing up on Wayland

* Fix documentation links

* Small documentation fixes

* Add note about doing stuff after StartCause::Init on macOS
2022-06-11 18:57:19 +02:00
Mads Marquart
6474891f1e Fix macOS 32bit (#2327) 2022-06-11 03:43:51 +02:00
Mads Marquart
40abb526cc Remove core-video-sys dependency (#2326)
Hasn't been updated in over 2 years - many open PRs, seems abandoned. Is the cause of several duplicate dependencies in our dependency tree!
2022-06-11 02:37:46 +02:00
Mads Marquart
c532d910c0 Build docs on docs.rs for iOS and Android as well (#2324) 2022-06-11 00:45:24 +02:00
Mads Marquart
44288f6280 Make WindowAttributes private (#2134)
* Make `WindowAttributes` private, and move its documentation

* Reorder WindowAttributes title and fullscreen to match method order
2022-06-10 19:05:28 +02:00
Kirill Chibisov
eec84ade86 Make set_device_event_filter non-mut
Commit f10a984 added `EventLoopWindowTarget::set_device_event_filter`
with for a mutable reference, however most winit APIs work with
immutable references, so altering API to play nicely with existing APIs.

This also disables device event filtering on debug example.
2022-06-10 15:39:02 +03:00
Kirill Chibisov
10419ff441 Run clippy on CI
Fixes #1402.
2022-06-10 13:43:33 +03:00
Marijn Suijten
57981b533d On Android, use HasRawWindowHandle directly from the ndk crate (#2318)
The `ndk` crate now implements [`HasRawWindowHandle` directly on
`NativeWindow`], relieving the burden to reimplement it on `winit`.

[`HasRawWindowHandle` directly on `NativeWindow`]: https://github.com/rust-windowing/android-ndk-rs/pull/274
2022-06-10 11:37:06 +02:00
tinaun
c5eaa0ab69 macOS: Emit LoopDestroyed on CMD+Q (#2073)
override applicationWillTerminate:

Co-authored-by: Mads Marquart <mads@marquart.dk>
2022-06-09 16:08:52 +02:00
James Liu
2c01e9e747 Migrate from lazy_static to once_cell 2022-06-08 21:50:26 +03:00
Kevin King
4c39b3188c Remove old dialog fix that is superseded by #2027 (#2292)
This fixes the run_return loop never returning on macos when using multiple windows
2022-06-08 17:22:54 +02:00
Kirill Chibisov
224872ce03 Prevent null dereference on X11 with bad locale 2022-06-08 01:04:33 +03:00
Christian Duerr
f10a984ba3 Add X11 opt-in function for device events
Previously on X11, by default all global events were broadcasted to
every winit application. This unnecessarily drains battery due to
excessive CPU usage when moving the mouse.

To resolve this, device events are now ignored by default and users must
manually opt into it using
`EventLoopWindowTarget::set_filter_device_events`.

Fixes (#1634) on Linux.
2022-06-08 00:17:45 +03:00
Lucas Kent
c7f7181388 Set WindowBuilder to must_use 2022-06-07 23:55:57 +03:00
Kirill Chibisov
92530299eb Revert "On Wayland, fix resize not propagating properly"
This reverts commit 78e5a395da.

It was discovered that in some cases mesa will lock the back
buffer, e.g. when making context current, leading to resize
missing. Given that applications can restructure their rendering
to account for that, and that winit isn't limited to playing
nice with mesa reverting the original commit.
2022-06-05 22:19:27 +03:00
Markus Siglreithmaier
58cd23d1ac On Windows, fix reported cursor position. (#2311)
When clicking and moving the cursor out of the window negative coordinates were not handled correctly.
2022-06-02 19:08:54 +02:00
Aron Parker
5d85c10a2c [Windows] Avoid GetModuleHandle(NULL) (#2301)
Use get_instance_handle() over GetModuleHandle(NULL)
2022-05-29 17:12:46 +02:00
Kevin Reid
f11270dac0 Reorganize EventLoopBuilder::build() platform documentation
Since there's a "Platform-specific" header, it makes sense to put the
Linux-specific part under it. On the other hand, "Can only be called on
the main thread." is true for all platforms, not just iOS, so there is
no reason to call it out for iOS specifically.
2022-05-29 14:51:27 +03:00
Phillip Hellewell
bcd76d4718 On macOS, emit resize event on frame_did_change
When the window switches mode from normal to tabbed one, it doesn't
get resized, however the frame gets resized. This commit makes
winit to track resizes when frame changes instead of window.

Fixes #2191.
2022-05-23 22:53:07 +03:00
Liam Murphy
4dd2b66aaa Fix warnings on nightly rust (#2295)
This was causing CI to fail: https://github.com/rust-windowing/winit/runs/6506026326
2022-05-20 17:03:35 +02:00
Bartłomiej Maryńczak
829a140d9b On Wayland, provide option for better CSD
While most compositors provide server side decorations, the GNOME
does not, and won't provide them. Also Wayland clients must render
client side decorations.

Winit was already drawing some decorations, however they were bad
looking and provided no text rendering, so the title was missing.
However this commit makes use of the SCTK external frame similar to
GTK's Adwaita theme supporting text rendering and looking similar to
other GTK applications.

Fixes #1967.
2022-05-20 03:09:23 +03:00
Kirill Chibisov
f04fa5d54f Add new Ime event for desktop platforms
This commit brings new Ime event to account for preedit state of input
method, also adding `Window::set_ime_allowed` to toggle IME input on
the particular window.

This commit implements API as designed in #1497 for desktop platforms.

Co-authored-by: Artur Kovacs <kovacs.artur.barnabas@gmail.com>
Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
Co-authored-by: Murarth <murarth@gmail.com>
Co-authored-by: Yusuke Kominami <yukke.konan@gmail.com>
Co-authored-by: moko256 <koutaro.mo@gmail.com>
2022-05-07 05:29:25 +03:00
Markus Siglreithmaier
b4175c1454 Bump windows-sys version to 0.36 (#2277) 2022-05-03 12:39:29 +02:00
Tobias Menzi
0728105b2b On Wayland, fix hiding cursors on GNOME
`wl_pointer::set_cursor` expects a serial number of the last
`wl_pointer::enter` event. However other calls expect latest
observed pointer serial, so this commit tracks both and
use them as required by specification.

Fixes #2273.
2022-05-01 16:21:34 +03:00
Kas
ea09d1d10e Fix embedded NULs in C wide strings returned from Windows API (#2264) 2022-04-30 13:58:51 +02:00
Kas
e7f88588bf Fix assigning the wrong monitor when receiving Windows move events (#2266)
Co-authored-by: kas <exactly-one-kas@users.noreply.github.com>
2022-04-30 13:21:08 +02:00
Kirill Chibisov
ce890c3455 Unify behavior of resizable across platforms
This makes X11 and Wayland follow Windows and macOS, so the size of the
window could be set even though it has resizable attribute set to false.

Fixes #2242.
2022-04-24 23:35:18 +03:00
Kirill Chibisov
cbba00d360 Unify with_app_id and with_class methods
Both APIs are used to set application name. This commit unifies the API
between Wayland and X11, so downstream applications can remove platform
specific code when using `WindowBuilderExtUnix`.

Fixes #1739.
2022-04-20 01:56:56 +03:00
Markus Siglreithmaier
bf366cb99d Add cursor hittest window functionality (#2232)
Co-authored-by: z4122 <412213484@qq.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2022-04-12 19:10:46 +02:00
Dusty DeWeese
142d55ff24 Always send RedrawEventsCleared on iOS
This makes it consistent with the rest of the platforms in winit.
2022-04-11 20:51:21 +03:00
Simon Hausmann
a58400a82c Fix TouchPhase::Ended reporting on Wayland
When all the receive from the compositor is `TouchEvent::Down` and
`TouchEvent::Up` for the same id, we would record the touch position in
the touch_points vector the first time. On `TouchEvent::Up` we'd find it
and report `TouchPhase::Ended` with that location. The next time we
receive `TouchEvent::Down` for the same id, we'd however unconditionally
append a new `TouchPoint` to `inner.touch_points`, with the new
position. On release however we'd find the earlier point and report its
location, which basically means that `TouchPhase::Ended` always and
forever reported the location of the very first touch down event.

Instead, this patch updates an existing touch point location with the
same id on `TouchDown`.

Fixes #1996
2022-04-11 20:13:28 +03:00
Kirill Chibisov
aac28d24ac Force SCTK version to prevent pointer leaking
SCTK versions before 15.4 were leaking pointers when window got closed.
So the more windows you've got the pointers you'll keep around.

Fixes #2248.
2022-04-10 13:42:05 +03:00
Steve Wooster
d624a3e648 Add methods to set ControlFlow operation
This allows downstream users to avoid importing `ControlFlow` from winit.
2022-04-10 04:32:02 +03:00
Kirill Chibisov
c57294b41a On Wayland commit after setting all startup props
The commit without buffer attached should be done after setting all
top level size/application related properties. While c4df7ad7a added
commit after setting decorations, it accounted just for decorations,
however we should account for the min/max size and other attributes.
2022-04-10 03:56:01 +03:00
Kirill Chibisov
485e82dcb1 Add getters for visible, resizeable, etc on X11
This commit adds `Window::is_visible`, `Window::is_decorated`, and
`Window::is_resizable` APIs on X11.
2022-04-10 01:51:54 +03:00
Kirill Chibisov
e8d910ffd3 Add is_resizable and is_decorated on Wayland
This commit brings `is_resizable` and `is_decorated`. Since the client
is responsible for both of them, they could be tracked without deep
syncing with server.
2022-04-07 03:05:11 +03:00
Amr Bashir
ab1f636960 feat(Windows): add skip taskbar methods (#2177) 2022-04-01 20:21:09 +02:00
TÖRÖK Attila
52c4670237 android: Add mapping from NDK Keycode to VirtualKeyCode (#2226) 2022-04-01 18:16:59 +02:00
Benjamin Saunders
2ae12fb0a0 Discourage use of WaitUntil to implement VSync (#2230) 2022-03-31 22:38:02 +02:00
Daniel Müller
6c1d3c4fd8 Fix scale factor calculation when the only monitor is reconnected
The scale factor being sent when the only monitor is disconnected and
reconnected is hard coded to 1.0. That may work by chance, if that's the
scale factor in use currently, but it does not work in the general case.
As a result, clients may end up with wrongly scaled or laid out window
contents after reconnect, as was reported over in
https://github.com/alacritty/alacritty/issues/5703, for example.

The problem was introduced by change 125ee0b, which caused an additional
ScaleFactorChanged event to be sent on monitor reconnect, but got the
scale factor wrong when the only monitor is disconnected and
reconnected.
This change fixes the problem by using the current monitor's scale
factor in this case. The event is still being sent as intended by
125ee0b.

Fixes #2123.
2022-03-31 17:43:48 +03:00
Amr Bashir
08de2b3fc4 feat(Windows): add with_msg_hook (#2213) 2022-03-30 10:30:45 +02:00
Markus Siglreithmaier
945a9e3122 Windows: Remove owned DC context per window (#1910)
The flag is not required for OpenGL and comes with several limitations when used with other style flags
2022-03-26 22:59:13 +01:00
Johan Andersson
6e28ba8927 Fix fullscreen window messaging race on Windows (#2225) 2022-03-26 16:43:13 +01:00
Lucas Kent
7369551c02 Clippy fixes for windows platform (#2131) 2022-03-23 19:08:04 +01:00
Mads Marquart
e22c76b3ac macOS set_ime_position fixes (#2180)
* Use NSView's inputContext instead of creating our own

This means that `set_ime_position` now properly invalidates the character coordinates.

* Make `set_ime_position` robust against moving windows
2022-03-18 14:50:24 +01:00
Mads Marquart
a438091266 Rename internal structs for consistency (#2149)
Proxy -> EventLoopProxy
Id -> WindowId or DeviceId
WindowTarget -> EventLoopWindowTarget
Handle -> MonitorHandle
Mode -> VideoMode
PlatformSpecificBuilderAttributes -> PlatformSpecificWindowBuilderAttributes
SuperWindowId -> RootWindowId
2022-03-18 14:09:39 +01:00
Emil Ernerfeldt
85baf79d17 Reverse horizontal scroll direction (#2105) 2022-03-13 14:22:02 +01:00
Kirill Chibisov
1c68be0631 On Wayland, fix consecutive run_return not polling
If you try to use `EventLoop::run_return` API in a way that you do on
demand polling of events it won't actually poll, since in such strategy
the `ControlFlow::Exit` is sent right before Wayland backend starts to
poll.

This was observed with smithay compositor Anvil which was doing this
particular thing leading to GNOME thinking that app isn't responding,
due to connection not being polled.
2022-03-11 18:15:33 +03:00
Clemens Wasser
b222dde835 Adopt windows-sys (#2057) 2022-03-07 22:58:12 +01:00
Kirill Chibisov
78e5a395da On Wayland, fix resize not propagating properly
On Wayland window size and scaling are double buffered, so winit should
send redraw requested for a client on the next frame as well.
2022-02-28 11:47:38 +03:00
Chris Copeland
7846e6a31e Update Window::is_maximized doc about X11/Wayland
The support for `Window::is_maximized` on X11/Wayland was added in c916eb6,
however the doc comment on the method was stating that it's not supported.
2022-02-28 11:19:16 +03:00
Lucas Kent
b7e7755edd Improve web example (#2115)
* Improve web example

* Implement basic logger into the example webpage

* Repace bash script with xtask

* replace wasm-bindgen-cli with wasm-bindgen-cli-support

* refactor

* Move logic into external crate.

* Remove CI changes

* Review feedback
2022-02-25 12:57:46 +01:00
Markus Røyset
40f48cbeb4 Fix unsafe_op_in_unsafe_fn warning on nightly (#2207) 2022-02-25 12:27:52 +01:00
Nikolai Kuklin
fb8313aa97 Fix typo in deprecation message (#2199) 2022-02-19 16:21:37 +01:00
Amr Bashir
f9643917d3 feat: add Window::is_visible (#2169)
* feat: add `Window::is_visible`

* use `Option<bool>`

* update doc

* move it right after `set_visible`
2022-02-17 19:44:14 +01:00
Mads Marquart
ac1c9b1218 Fix Android CI (#2197)
Fixes https://github.com/rust-windowing/winit/issues/2196 until a better solution using `ndk-context` is possible
2022-02-17 18:50:18 +01:00
Amr Bashir
daf0d6b9a7 feat: add Window::is_resizable (#2171)
* feat: add `Window::is_resizable`

* move it right after `set_resizable`
2022-02-17 16:03:17 +01:00
Amr Bashir
fa14863284 feat: add Window::is_decorated (#2172)
* feat: add `Window::is_decorated`

* move it right after `set_decorations`
2022-02-17 14:31:13 +01:00
Mads Marquart
cd9ec0afc7 Bump dev-dependencies (#2181)
* Update image 0.23 -> 0.24 and simple_logger 1.9 -> 2.1

* Reduce feature set in `image` dev-dependency
2022-02-17 14:13:32 +01:00
Mads Marquart
f3f6f1008a Add EventLoopBuilder
This commit adds an `EventLoopBuilder` struct to simplify event loop
customization and providing options to it upon creation. It also
deprecates the use of `EventLoop::with_user_event` in favor of the same
method on new builder, and replaces old platforms specific extension
traits with the new ones on the `EventLoopBuilder`.
2022-02-17 00:09:03 +03:00
Artúr Kovács
0e52672f4a On X11, Fix for repeated event loop iteration when ControlFlow was Wait (#2155)
* On X11, Fix for repeated event loop iteration
when `ControlFlow` was `Wait`

* ControlFlow::Poll now runs continously as should
2022-02-04 12:13:04 +01:00
Lassi Pulkkinen
bc1dc1fd63 On Wayland, report unaccelerated mouse deltas in DeviceEvent::MouseMotion 2022-02-03 13:46:29 +03:00
David Ackerman
f93f2c158b Bump versions of ndk to 0.6, ndk-sys to 0.3, ndk-glue to 0.6 (#2163) 2022-02-01 00:14:36 +01:00
Mads Marquart
9229e2d88b macOS RAII trace guards (#2150)
* Add TraceGuard to make tracing simpler

* Add SharedStateMutexGuard to make tracing simpler

* Add trace_scope macro

* Add missing let binding in trace_scope!
2022-01-23 21:35:26 +01:00
Mads Marquart
51bb6b751e Remove WINIT_LINK_COLORSYNC (no longer needed) (#2136)
Since https://github.com/rust-windowing/winit/pull/2078 we link to `ApplicationServices`, which contains the `CGDisplayCreateUUIDFromDisplayID` symbol in all versions.
2022-01-23 20:38:08 +01:00
Mads Marquart
2cc87cab65 Update changelog guidelines to prevent conflicts from blocking PRs (#2145)
* Update changelog guidelines to prevent conflicts from blocking PRs

As per consensus in https://github.com/rust-windowing/winit/issues/2135

* Add note about whitespace in changelog

* Add note about maintainer creating new tag
2022-01-23 13:55:33 +01:00
Benjamin Brittain
7cd273ae58 Make WindowBuilder's with_app_id method more ergonomic 2022-01-22 03:42:46 +03:00
Lucas Kent
001fb7ef60 Clippy fixes macos platform (#2133) 2022-01-16 01:14:59 +01:00
Lucas Kent
cf4660841a Update to Rust 2021 Edition (#2114) 2022-01-13 06:59:57 +01:00
multisn8
a52f755ce8 Add exit code to ControlFlow::Exit (#2100)
* Add exit code to control flow and impl on linux

* Fix examples to have an exit code

* Fix doc examples to use an exit code

* Improve documentation wording on the exit code

* Add exit code example

* Add exit code on windows

* Change i32 as exit code to u8

This avoids nasty surprises with negative numbers on some unix-alikes
due to two's complement.

* Fix android usages of ControlFlow::Exit

* Fix ios usages of ControlFlow::Exit

* Fix web usages of ControlFlow::Exit

* Add macos exit code

* Add changelog note

* Document exit code on display server disconnection

* Revert "Change i32 as exit code to u8"

This reverts commit f88fba0253.

* Change Exit to ExitWithCode and make an Exit const

* Revert "Add exit code example"

This reverts commit fbd3d03de9.

* Revert "Fix doc examples to use an exit code"

This reverts commit daabcdf9ef.

* Revert "Fix examples to have an exit code"

This reverts commit 0df486896b.

* Fix unix-alike to use ExitWithCode instead of Exit

* Fix windows to use ExitWithCode rather than Exit

* Silence warning about non-uppercase Exit const

* Refactor exit code handling

* Fix macos Exit usage and recover original semantic

* Fix ios to use ExitWithCode instead of Exit

* Update documentation to reflect ExitWithCode

* Fix web to use ExitWithCode when needed, not Exit

* Fix android to use ExitWithCode, not Exit

* Apply documenation nits

* Apply even more documentation nits

* Move change in CHANGELOG.md under "Unreleased"

* Try to use OS error code as exit code on wayland
2022-01-11 01:23:20 +01:00
Mads Marquart
2a2abc4843 Fix some invalid msg_send! usage (#2138)
* Fix some invalid msg_send! usage

* Make the implementation of superclass clearer
2022-01-10 21:39:17 +01:00
Lucas Kent
8af222c1e3 Remove entries from PULL_REQUEST_TEMPLATE.md that are now covered by CI. (#2126) 2022-01-05 16:23:56 +01:00
Artúr Kovács
d3e6949007 Release 0.26.1 (#2125) 2022-01-05 15:38:59 +01:00
Mads Marquart
a033b25ecb Make CI run on changes to non-code files as well (#2130)
The repository is configured such that specific checks are required to pass before merging; since CI doesn't run on PRs that don't change code, they can't be merged without administrator intervention.

Also, while this definition is currently correct, we might in the future change something so that changes to other files (like markdown files) really should be run through CI.

And example of that could be:
```rust
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
extern "C" {}
```
2022-01-05 15:01:17 +01:00
Mads Marquart
39dd30c239 Fix CGDisplayCreateUUIDFromDisplayID linking (again) (#2078)
See also https://github.com/rust-windowing/winit/pull/1626.

The `cocoa` crate links to AppKit, which made the symbol `CGDisplayCreateUUIDFromDisplayID` from ApplicationServices/ColorSync (which AppKit uses internally) available to us on macOS 10.8 to 10.13.

However, this does not work on macOS 10.7 (where AppKit does not link to ColorSync internally). Instead of relying on this, we should just link to ApplicationServices directly.
2022-01-05 14:38:24 +01:00
TotalKrill
c5c99d2357 Add cursor grab for web target (#2025)
* Add cursor grab

* Update feature matrix, changelog and platform information

* Add proper error propagation

* Remove "expect" from fallible code

    code would crash if handling pointer capture outside of winit on
    every mouse click

    we swallow the error since we could not think of a case where this
    would fail in a way that would want to handle that it fails

* Remove unnecessary implementation comment

Co-authored-by: Will Crichton <wcrichto@cs.stanford.edu>
2022-01-05 11:13:46 +01:00
Shuoliu Yang
25ff30ee8c Fix transparent window crash on Windows 11 (#2121)
Maybe the transparent setting in WM_NCCREATE on Windows 11 will cause a block when calling DwmEnableBlureBehindWindow and will crash. Puts them into WM_CREATE and it works.
2022-01-03 13:54:31 +01:00
Artúr Kovács
6b250a74f8 Revert "Add composition event on macOS (#1979)" (#2119)
This reverts commit 8afeb910bd.

Reverting because this change made Pinyin input unusable
(only latin characters showed even after selecting the
desired Chinese character)
2022-01-02 22:01:51 +01:00
Lucas Kent
5331397c6c Provide examples for all window position/size setters (#2107) 2022-01-02 04:56:13 +01:00
Lucas Kent
0b39024133 Fix clippy warnings (#2108)
* Fix clippy warnings

* review feedback.
2022-01-01 03:00:11 +01:00
Mika
438d286fd5 Add new mappings for numlock, numpadenter and numpadcomma on X11 (#1937)
* Add X11 mappings

* Update CHANGELOG.md

Co-authored-by: Markus Røyset <maroider@protonmail.com>

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-12-11 03:14:31 +01:00
Philippe Renon
18a61f1058 Fix warnings (#2076)
* examples: Fix unused `Result` that must be used when initializing console_log

* examples: Fix unused imports

* Fix unread name field warning in linux x11 ime InputMethod struct

* Fix unread name field warning in linux x11 Device struct

* Ignore unread field warning in macos/ios MonitorHandle struct

* ci: Add `--deny warnings` to `RUSTFLAGS`
2021-12-11 03:02:48 +01:00
Markus Røyset
20d012ae3f Update contact links (#2079) 2021-12-07 11:27:30 -08:00
Mads Marquart
efc54ab8ba Add note about cargo update to the raw-window-handle changelog update (#2086) 2021-12-06 12:53:56 -08:00
141 changed files with 8237 additions and 4770 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[alias]
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]

2
.gitattributes vendored
View File

@@ -20,5 +20,3 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
/CHANGELOG.md merge=union

View File

@@ -1,7 +1,4 @@
- [ ] Tested on all platforms changed
- [ ] Compilation warnings were addressed
- [ ] `cargo fmt` has been run on this branch
- [ ] `cargo doc` builds successfully
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality

View File

@@ -2,16 +2,8 @@ name: CI
on:
pull_request:
paths:
- '**.rs'
- '**.toml'
- '.github/workflows/ci.yml'
push:
branches: [master]
paths:
- '**.rs'
- '**.toml'
- '.github/workflows/ci.yml'
jobs:
Check_Formatting:
@@ -25,12 +17,14 @@ jobs:
- name: Check Formatting
run: cargo +stable fmt --all -- --check
Tests:
tests:
name: Tests
strategy:
fail-fast: false
matrix:
rust_version: [stable, nightly]
rust_version: [1.57.0, stable, nightly]
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
- { target: i686-pc-windows-msvc, os: windows-latest, }
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
@@ -50,10 +44,12 @@ jobs:
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
PKG_CONFIG_ALLOW_CROSS: 1
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
CMD: ${{ matrix.platform.cmd }}
RUSTDOCFLAGS: -Dwarnings
runs-on: ${{ matrix.platform.os }}
steps:
@@ -69,18 +65,26 @@ jobs:
with:
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Setup NDK path
shell: bash
# "Temporary" workaround until https://github.com/actions/virtual-environments/issues/5879#issuecomment-1195156618
# gets looked into.
run: echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> $GITHUB_ENV
- 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 apt-get update && sudo apt-get install gcc-multilib
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libfreetype6-dev:i386 libfontconfig1-dev:i386
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk
- name: Check documentation
shell: bash
if: matrix.platform.target != 'wasm32-unknown-unknown'
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
- name: Build
shell: bash
@@ -97,6 +101,10 @@ jobs:
!contains(matrix.platform.target, 'wasm32'))
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')
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
- name: Build with serde enabled
shell: bash

View File

@@ -1,6 +1,102 @@
# Changelog
All notable changes to this project will be documented in this file.
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
And please only add new entries to the top of this list, right below the `# Unreleased` header.
# Unreleased
# 0.27.1 (2022-07-30)
- The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested.
- On X11, fix crash on start due to inability to create an IME context without any preedit.
# 0.27.0 (2022-07-26)
- On Windows, fix hiding a maximized window.
- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`.
- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception.
- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11.
- On X11, fix events for caps lock key not being sent
- Build docs on `docs.rs` for iOS and Android as well.
- **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`.
- Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute.
- On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q.
- On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning.
- On Wayland, fix bug where the cursor wouldn't hide in GNOME.
- On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events.
- On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`.
- On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`.
- On Windows, remove internally unique DC per window.
- On macOS, remove the need to call `set_ime_position` after moving the window.
- Added `Window::is_visible`.
- Added `Window::is_resizable`.
- Added `Window::is_decorated`.
- On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait`
- On X11, fix scale factor calculation when the only monitor is reconnected
- On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`.
- On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends.
- **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`.
- Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable.
- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
- Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it.
- Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead.
- **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods).
- **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods).
- **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods).
- **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies.
- The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings.
- On Wayland, fix polling during consecutive `EventLoop::run_return` invocations.
- On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen`
- On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found.
- Added helper methods on `ControlFlow` to set its value.
- On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor
sent a cancel or frame event.
- On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms.
- **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`.
- On Wayland, fallback CSD was replaced with proper one:
- `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder.
- `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window.
- `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting.
- `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library.
- `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering.
- On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`.
- On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages
- **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms.
- Added `Window::set_ime_allowed` supported on desktop platforms.
- **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled.
- On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`.
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
- Implemented `Default` on `EventLoop<()>`.
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
- On Wayland, add support for `Window::set_cursor_position`.
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision.
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
- On Windows, fix focus events being sent to inactive windows.
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
- 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.
# 0.26.1 (2022-01-05)
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.
- On Web, implement cursor grabbing through the pointer lock API.
- On X11, add mappings for numpad comma, numpad enter, numlock and pause.
- On macOS, fix Pinyin IME input by reverting a change that intended to improve IME.
- On Windows, fix a crash with transparent windows on Windows 11.
# 0.26.0 (2021-12-01)
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74).
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading.
- On X11, bump `mio` to 0.8.
- On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`.
- On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0.
@@ -213,13 +309,13 @@
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
- **Breaking**: Overhaul how Winit handles DPI:
+ Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
+ Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
+ `hidpi_factor` has been renamed to `scale_factor`.
+ `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
- Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
- Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
- `hidpi_factor` has been renamed to `scale_factor`.
- `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
resizes the window in response to the change.
+ On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
+ `Size` and `Position` types are now generic over their exact pixel type.
- On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
- `Size` and `Position` types are now generic over their exact pixel type.
# 0.20.0 Alpha 6 (2020-01-03)
@@ -324,7 +420,7 @@
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants.
- Adds support for exclusive fullscreen mode.
- Adds support for exclusive fullscreen mode.
- On iOS, add support for hiding the home indicator.
- On iOS, add support for deferring system gestures.
- On iOS, fix a crash that occurred while acquiring a monitor's name.
@@ -523,7 +619,7 @@ and `WindowEvent::HoveredFile`.
# Version 0.16.1 (2018-07-02)
- Added logging through `log`. Logging will become more extensive over time.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This _greatly_ cuts back on unsightly auto-resizing that would occur immediately after window creation.
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
# Version 0.16.0 (2018-06-25)
@@ -673,7 +769,7 @@ and `WindowEvent::HoveredFile`.
# Version 0.10.1 (2018-02-05)
*Yanked*
_Yanked_
# Version 0.10.0 (2017-12-27)

View File

@@ -20,12 +20,16 @@ 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.
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
handle those for you when merging (see below).
- if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
@@ -34,9 +38,26 @@ Once your PR is open, you can ask for review by a maintainer of your platform. W
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
"give contributors write access to the branch" checkbox when creating the PR.
## Maintainers & Testers
The current [list of testers and contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors)
can be found on the Wiki.
If you are interested in contributing or testing on a platform, please add yourself to that table!
## Making a new release
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).
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.
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.

View File

@@ -1,44 +1,63 @@
[package]
name = "winit"
version = "0.26.0"
version = "0.27.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2018"
edition = "2021"
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.57.0"
[package.metadata.docs.rs]
features = ["serde"]
default-target = "x86_64-unknown-linux-gnu"
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"]
# These are all tested in CI
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"x86_64-apple-ios",
# Android
"aarch64-linux-android",
# WebAssembly
"wasm32-unknown-unknown",
]
[features]
default = ["x11", "wayland", "wayland-dlopen"]
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"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
[dependencies]
instant = { version = "0.1", features = ["wasm-bindgen"] }
lazy_static = "1"
once_cell = "1.12"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.4.2"
raw-window-handle = "0.5.0"
bitflags = "1"
mint = { version = "0.5.6", optional = true }
[dev-dependencies]
image = "0.23.12"
simple_logger = "1.9"
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = "2.1.0"
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.5"
ndk-sys = "0.2.0"
ndk-glue = "0.5"
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
ndk = "0.7.0"
ndk-glue = "0.7.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc = "0.2.7"
@@ -48,51 +67,48 @@ cocoa = "0.24"
core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2.0"
block = "0.1"
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
version = "0.1.4"
default_features = false
features = ["display_link"]
[target.'cfg(target_os = "windows")'.dependencies]
parking_lot = "0.11"
parking_lot = "0.12"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.9"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.36"
features = [
"combaseapi",
"commctrl",
"dwmapi",
"errhandlingapi",
"imm",
"hidusage",
"libloaderapi",
"objbase",
"ole2",
"processthreadsapi",
"shellapi",
"shellscalingapi",
"shobjidl_core",
"unknwnbase",
"winbase",
"windowsx",
"winerror",
"wingdi",
"winnt",
"winuser",
"mmsystem",
"timeapi"
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"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", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29", features = [ "staging_protocols"], optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.15.1", default_features = false, features = ["calloop"], optional = true }
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.11.0", optional = true }
parking_lot = { version = "0.12.0", optional = true }
libc = "0.2.64"
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
@@ -126,3 +142,8 @@ version = "0.2.45"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_log = "0.2"
[workspace]
members = [
"run-wasm",
]

View File

@@ -100,8 +100,10 @@ If your PR makes notable changes to Winit's features, please update this section
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
@@ -172,7 +174,7 @@ Legend:
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**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**|
@@ -196,9 +198,11 @@ Legend:
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ | |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**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 |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.26.0"
winit = "0.27.1"
```
## [Documentation](https://docs.rs/winit)
@@ -19,9 +19,8 @@ For features _outside_ the scope of winit, see [Missing features provided by oth
Join us in any of these:
[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org)
[![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit)
## Usage
@@ -70,8 +69,18 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
### Platform-specific usage
#### Wayland
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`
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
On the web platform, a Winit window is backed by a `<canvas>` element. You can
@@ -92,15 +101,16 @@ book].
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 `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.
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.
`ndk_glue` <-> `winit` version comparison compatibility:
`winit` compatibility table with `ndk-glue`:
| winit | 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.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"` |
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
@@ -122,10 +132,13 @@ And run the application with `cargo apk run --example request_redraw_threaded`
#### MacOS
To ensure compatibility with older MacOS systems, winit links to
CGDisplayCreateUUIDFromDisplayID through the CoreGraphics framework.
However, under certain setups this function is only available to be linked
through the newer ColorSync framework. So, winit provides the
`WINIT_LINK_COLORSYNC` environment variable which can be set to `1` or `true`
while compiling to enable linking via ColorSync.
A lot of functionality expects the application to be ready before you start
doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::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

View File

@@ -1,10 +0,0 @@
fn main() {
// If building for macos and WINIT_LINK_COLORSYNC is set to true
// use CGDisplayCreateUUIDFromDisplayID from ColorSync instead of CoreGraphics
if std::env::var("CARGO_CFG_TARGET_OS").map_or(false, |os| os == "macos")
&& std::env::var("WINIT_LINK_COLORSYNC")
.map_or(false, |v| v == "1" || v.eq_ignore_ascii_case("true"))
{
println!("cargo:rustc-cfg=use_colorsync_cgdisplaycreateuuidfromdisplayid");
}
}

View File

@@ -1,9 +1,11 @@
#![allow(clippy::single_match)]
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -88,23 +90,21 @@ fn main() {
window.request_redraw();
}
if close_requested {
*control_flow = ControlFlow::Exit;
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {}
Event::RedrawEventsCleared => {
*control_flow = match mode {
Mode::Wait => ControlFlow::Wait,
match mode {
Mode::Wait => control_flow.set_wait(),
Mode::WaitUntil => {
if wait_cancelled {
*control_flow
} else {
ControlFlow::WaitUntil(instant::Instant::now() + WAIT_TIME)
if !wait_cancelled {
control_flow.set_wait_until(instant::Instant::now() + WAIT_TIME);
}
}
Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME);
ControlFlow::Poll
control_flow.set_poll();
}
};
}

View File

@@ -1,7 +1,9 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::{CursorIcon, WindowBuilder},
};
@@ -15,7 +17,7 @@ fn main() {
let mut cursor_idx = 0;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent {
@@ -42,8 +44,7 @@ fn main() {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
return;
control_flow.set_exit();
}
_ => (),
}

View File

@@ -1,8 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
event_loop::EventLoop,
window::{CursorGrabMode, WindowBuilder},
};
fn main() {
@@ -17,11 +19,11 @@ fn main() {
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
@@ -32,11 +34,23 @@ fn main() {
..
} => {
use winit::event::VirtualKeyCode::*;
match key {
Escape => *control_flow = ControlFlow::Exit,
G => window.set_cursor_grab(!modifiers.shift()).unwrap(),
H => window.set_cursor_visible(modifiers.shift()),
_ => (),
let result = match 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(())
}
_ => Ok(()),
};
if let Err(err) = result {
println!("error: {}", err);
}
}
WindowEvent::ModifiersChanged(m) => modifiers = m,

View File

@@ -1,9 +1,11 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoopBuilder,
window::WindowBuilder,
};
@@ -13,7 +15,7 @@ fn main() {
}
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let _window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -34,14 +36,14 @@ fn main() {
});
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::UserEvent(event) => println!("user event: {:?}", event),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
} => control_flow.set_exit(),
_ => (),
}
});

View File

@@ -1,9 +1,11 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::{Window, WindowBuilder, WindowId},
};
@@ -22,7 +24,7 @@ fn main() {
eprintln!("Switch which window is to be dragged by pressing \"x\".")
}
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,

View File

@@ -1,69 +1,107 @@
use std::io::{stdin, stdout, Write};
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::monitor::{MonitorHandle, VideoMode};
use winit::event_loop::EventLoop;
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let fullscreen = Some(match num {
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))),
_ => panic!("Please enter a valid number"),
});
let mut decorations = true;
let mut minimized = false;
let window = WindowBuilder::new()
.with_title("Hello world!")
.with_fullscreen(fullscreen.clone())
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
let mut monitor_index = 0;
let mut monitor = event_loop
.available_monitors()
.next()
.expect("no monitor found!");
println!("Monitor: {:?}", monitor.name());
let mut mode_index = 0;
let mut mode = monitor.video_modes().next().expect("no mode found");
println!("Mode: {}", mode);
println!("Keys:");
println!("- Esc\tExit");
println!("- F\tToggle exclusive fullscreen mode");
println!("- B\tToggle borderless mode");
println!("- S\tNext screen");
println!("- M\tNext mode for this screen");
println!("- D\tToggle window decorations");
println!("- X\tMaximize window");
println!("- Z\tMinimize window");
event_loop.run(move |event, elwt, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(virtual_code),
state,
state: ElementState::Pressed,
..
},
..
} => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} => 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 {
window.set_fullscreen(fullscreen.clone());
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::S, ElementState::Pressed) => {
println!("window.fullscreen {:?}", window.fullscreen());
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);
}
(VirtualKeyCode::M, ElementState::Pressed) => {
VirtualKeyCode::D => {
decorations = !decorations;
window.set_decorations(decorations);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
(VirtualKeyCode::D, ElementState::Pressed) => {
decorations = !decorations;
window.set_decorations(decorations);
VirtualKeyCode::Z => {
minimized = !minimized;
window.set_minimized(minimized);
}
_ => (),
},
@@ -73,46 +111,3 @@ fn main() {
}
});
}
// Enumerate monitors and prompt user to choose one
fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
for (num, monitor) in event_loop.available_monitors().enumerate() {
println!("Monitor #{}: {:?}", num, monitor.name());
}
print!("Please write the number of the monitor to use: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = event_loop
.available_monitors()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {:?}", monitor.name());
monitor
}
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
for (i, video_mode) in monitor.video_modes().enumerate() {
println!("Video mode #{}: {}", i, video_mode);
}
print!("Please write the number of the video mode to use: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let video_mode = monitor
.video_modes()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {}", video_mode);
video_mode
}

View File

@@ -1,7 +1,9 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -21,7 +23,7 @@ fn main() {
ElementState::Released,
VirtualKeyCode::{N, Y},
};
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => {
@@ -63,7 +65,7 @@ fn main() {
// event loop (i.e. if it's a multi-window application), you need to
// drop the window. That closes it, and results in `Destroyed` being
// sent.
*control_flow = ControlFlow::Exit;
control_flow.set_exit();
}
}
N => {

99
examples/ime.rs Normal file
View File

@@ -0,0 +1,99 @@
#![allow(clippy::single_match)]
use log::LevelFilter;
use simple_logger::SimpleLogger;
use winit::{
dpi::PhysicalPosition,
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new()
.with_level(LevelFilter::Trace)
.init()
.unwrap();
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");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
.build(&event_loop)
.unwrap();
let mut ime_allowed = true;
window.set_ime_allowed(ime_allowed);
let mut may_show_ime = false;
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
cursor_position = position;
}
Event::WindowEvent {
event:
WindowEvent::MouseInput {
state: ElementState::Released,
..
},
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_position(ime_pos);
}
}
Event::WindowEvent {
event: WindowEvent::Ime(event),
..
} => {
println!("{:?}", event);
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_position(ime_pos);
}
}
Event::WindowEvent {
event: WindowEvent::ReceivedCharacter(ch),
..
} => {
println!("ch: {:?}", ch);
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
..
} => {
println!("key: {:?}", input);
if input.state == ElementState::Pressed
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
{
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME: {}\n", ime_allowed);
}
}
_ => (),
}
});
}

View File

@@ -1,30 +0,0 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View File

@@ -1,41 +0,0 @@
extern crate winit;
use simple_logger::SimpleLogger;
use winit::event::{Event, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
// Keyboard input event to handle minimize via a hotkey
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
window_id,
} => {
if window_id == window.id() {
// Pressing the 'M' key will minimize the window
if input.virtual_keycode == Some(VirtualKeyCode::M) {
window.set_minimized(true);
}
}
}
_ => (),
}
});
}

View File

@@ -1,3 +1,5 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{event_loop::EventLoop, window::WindowBuilder};

View File

@@ -1,7 +1,9 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -14,29 +16,41 @@ fn main() {
.build(&event_loop)
.unwrap();
println!(
r"
When using so called 'natural scrolling' (scrolling that acts like on a touch screen), this is what to expect:
Moving your finger downwards on a scroll wheel should make the window move down, and you should see a positive Y scroll value.
When moving fingers on a trackpad down and to the right, you should see positive X and Y deltas, and the window should move down and to the right.
With reverse scrolling, you should see the inverse behavior.
In both cases the example window should move like the content of a scroll area in any other application.
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseWheel { delta } => match delta {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(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;
pos.y -= (y * pixels_per_line) as i32;
pos.x += (x * pixels_per_line) as i32;
pos.y += (y * pixels_per_line) as i32;
window.set_outer_position(pos)
}
winit::event::MouseScrollDelta::PixelDelta(p) => {
println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y);
let mut pos = window.outer_position().unwrap();
pos.x -= p.x as i32;
pos.y -= p.y as i32;
pos.x += p.x as i32;
pos.y += p.y as i32;
window.set_outer_position(pos)
}
},

View File

@@ -1,3 +1,5 @@
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
@@ -6,8 +8,8 @@ fn main() {
use winit::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, Fullscreen, WindowBuilder},
event_loop::EventLoop,
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
@@ -34,10 +36,10 @@ fn main() {
// We need to update our chosen video mode if the window
// was moved to an another monitor, so that the window
// appears on this monitor instead when we go fullscreen
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
let previous_video_mode = video_modes.get(video_mode_id).cloned();
video_modes = window.current_monitor().unwrap().video_modes().collect();
video_mode_id = video_mode_id.min(video_modes.len());
let video_mode = video_modes.iter().nth(video_mode_id);
let video_mode = video_modes.get(video_mode_id);
// Different monitors may support different video modes,
// and the index we chose previously may now point to a
@@ -45,7 +47,7 @@ fn main() {
if video_mode != previous_video_mode.as_ref() {
println!(
"Window moved to another monitor, picked video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
video_modes.get(video_mode_id).unwrap()
);
}
}
@@ -77,19 +79,30 @@ fn main() {
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!(
"Picking video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
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.iter().nth(video_mode_id).unwrap().clone(),
)),
(true, true) => {
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
}
(false, _) => None,
}),
G => window.set_cursor_grab(state).unwrap(),
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 | 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:");
@@ -146,9 +159,9 @@ fn main() {
});
}
event_loop.run(move |event, _event_loop, control_flow| {
*control_flow = match !window_senders.is_empty() {
true => ControlFlow::Wait,
false => ControlFlow::Exit,
match !window_senders.is_empty() {
true => control_flow.set_wait(),
false => control_flow.set_exit(),
};
match event {
Event::WindowEvent { event, window_id } => match event {
@@ -173,7 +186,7 @@ fn main() {
}
}
},
_ => (),
_ => {}
}
})
}

View File

@@ -1,9 +1,11 @@
#![allow(clippy::single_match)]
use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
window::Window,
};
@@ -14,11 +16,14 @@ fn main() {
let mut windows = HashMap::new();
for _ in 0..3 {
let window = Window::new(&event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
println!("Press N to open a new window.");
event_loop.run(move |event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent { event, window_id } => {
@@ -30,18 +35,21 @@ fn main() {
windows.remove(&window_id);
if windows.is_empty() {
*control_flow = ControlFlow::Exit;
control_flow.set_exit();
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::N),
..
},
is_synthetic: false,
..
} => {
let window = Window::new(&event_loop).unwrap();
let window = Window::new(event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
_ => (),

View File

@@ -1,7 +1,9 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -17,11 +19,11 @@ fn main() {
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput {
state: ElementState::Released,
..

View File

@@ -1,14 +1,16 @@
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#![allow(clippy::single_match)]
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::{thread, time};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -25,13 +27,13 @@ fn main() {
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
}

View File

@@ -1,8 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -14,17 +16,19 @@ fn main() {
let window = WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_inner_size(LogicalSize::new(400.0, 200.0))
.with_inner_size(LogicalSize::new(600.0, 300.0))
.with_min_inner_size(LogicalSize::new(400.0, 200.0))
.with_max_inner_size(LogicalSize::new(800.0, 400.0))
.with_resizable(resizable)
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {

View File

@@ -1,54 +0,0 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::PhysicalPosition,
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_title("A fantastic window!");
println!("Ime position will system default");
println!("Click to set ime position to cursor's");
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
cursor_position = position;
}
Event::WindowEvent {
event:
WindowEvent::MouseInput {
state: ElementState::Released,
..
},
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
window.set_ime_position(cursor_position);
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
return;
}
_ => (),
}
});
}

View File

@@ -1,10 +1,12 @@
#![allow(clippy::single_match)]
use instant::Instant;
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -24,16 +26,16 @@ fn main() {
match event {
Event::NewEvents(StartCause::Init) => {
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length)
control_flow.set_wait_until(Instant::now() + timer_length);
}
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
control_flow.set_wait_until(Instant::now() + timer_length);
println!("\nTimer\n");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
} => control_flow.set_exit(),
_ => (),
}
});

View File

@@ -1,7 +1,9 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -18,14 +20,14 @@ fn main() {
window.set_title("A fantastic window!");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
} => control_flow.set_exit(),
_ => (),
}
});

View File

@@ -1,3 +1,5 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::event_loop::EventLoop;

View File

@@ -1,6 +1,8 @@
#![allow(clippy::single_match)]
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -13,30 +15,19 @@ pub fn main() {
.unwrap();
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
body.append_child(&canvas)
.expect("Append canvas to HTML body");
}
let log_list = wasm::create_log_list(&window);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
#[cfg(target_arch = "wasm32")]
log::debug!("{:?}", event);
wasm::log_event(&log_list, &event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
window.request_redraw();
}
@@ -48,11 +39,52 @@ pub fn main() {
#[cfg(target_arch = "wasm32")]
mod wasm {
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug);
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
#[allow(clippy::main_recursion)]
super::main();
}
pub fn create_log_list(window: &Window) -> web_sys::Element {
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
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;");
body.append_child(&canvas).unwrap();
let log_header = document.create_element("h2").unwrap();
log_header.set_text_content(Some("Event Log"));
body.append_child(&log_header).unwrap();
let log_list = document.create_element("ul").unwrap();
body.append_child(&log_list).unwrap();
log_list
}
pub fn log_event(log_list: &web_sys::Element, event: &Event<()>) {
log::debug!("{:?}", event);
// Getting access to browser logs requires a lot of setup on mobile devices.
// So we implement this basic logging system into the page to give developers an easy alternative.
// As a bonus its also kind of handy on desktop.
if let Event::WindowEvent { event, .. } = &event {
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_list
.insert_before(&log, log_list.first_child().as_ref())
.unwrap();
}
}
}

View File

@@ -1,7 +1,9 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
@@ -16,14 +18,14 @@ fn main() {
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
println!("{:?}", event);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
window.request_redraw();
}

View File

@@ -1,10 +1,12 @@
#![allow(clippy::single_match)]
// This example is used by developers to test various window functions.
use simple_logger::SimpleLogger;
use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::{DeviceEventFilter, EventLoop},
window::{Fullscreen, WindowBuilder},
};
@@ -30,8 +32,10 @@ fn main() {
let mut minimized = false;
let mut visible = true;
event_loop.set_device_event_filter(DeviceEventFilter::Never);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
match event {
Event::DeviceEvent {
@@ -58,67 +62,69 @@ fn main() {
_ => (),
},
Event::WindowEvent {
event: WindowEvent::KeyboardInput { input, .. },
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
},
..
},
..
} => match input {
KeyboardInput {
virtual_keycode: Some(key),
state: ElementState::Pressed,
..
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
} => match key {
VirtualKeyCode::E => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
VirtualKeyCode::F => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
VirtualKeyCode::F => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
VirtualKeyCode::P => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
VirtualKeyCode::P => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
}
VirtualKeyCode::Q => {
*control_flow = ControlFlow::Exit;
}
VirtualKeyCode::V => {
visible = !visible;
window.set_visible(visible);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
_ => (),
},
}
VirtualKeyCode::M => {
minimized = !minimized;
window.set_minimized(minimized);
}
VirtualKeyCode::Q => {
control_flow.set_exit();
}
VirtualKeyCode::V => {
visible = !visible;
window.set_visible(visible);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
} if window_id == window.id() => control_flow.set_exit(),
_ => (),
}
});

View File

@@ -1,10 +1,11 @@
extern crate image;
#![allow(clippy::single_match)]
use std::path::Path;
use simple_logger::SimpleLogger;
use winit::{
event::Event,
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::{Icon, WindowBuilder},
};
@@ -30,12 +31,12 @@ fn main() {
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
if let Event::WindowEvent { event, .. } = event {
use winit::event::WindowEvent::*;
match event {
CloseRequested => *control_flow = ControlFlow::Exit,
CloseRequested => control_flow.set_exit(),
DroppedFile(path) => {
window.set_window_icon(Some(load_icon(&path)));
}

View File

@@ -1,3 +1,5 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(
target_os = "windows",
@@ -6,7 +8,8 @@
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
target_os = "openbsd",
target_os = "android",
))]
fn main() {
use std::{thread::sleep, time::Duration};
@@ -14,7 +17,7 @@ fn main() {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
platform::run_return::EventLoopExtRunReturn,
window::WindowBuilder,
};
@@ -30,7 +33,7 @@ fn main() {
while !quit {
event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
@@ -45,7 +48,7 @@ fn main() {
quit = true;
}
Event::MainEventsCleared => {
*control_flow = ControlFlow::Exit;
control_flow.set_exit();
}
_ => (),
}
@@ -57,7 +60,7 @@ fn main() {
}
}
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
fn main() {
println!("This platform doesn't support run_return.");
}

9
run-wasm/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "run-wasm"
version = "0.1.0"
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"

3
run-wasm/src/main.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
cargo_run_wasm::run_wasm();
}

View File

@@ -35,8 +35,9 @@
//!
//! ### Position and Size types
//!
//! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the
//! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor.
//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the
//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels
//! divided by the scale factor.
//! All of Winit's functions return physical types, but can take either logical or physical
//! coordinates as input, allowing you to use the most convenient coordinate system for your
//! particular application.
@@ -46,19 +47,18 @@
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
//! will truncate the fractional part of the float, rather than properly round to the nearest
//! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the
//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
//! int, although rounding lessens the problem.
//!
//! ### Events
//!
//! Winit will dispatch a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged)
//! event whenever a window's scale factor has changed. This can happen if the user drags their
//! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their
//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how
//! the platform changes the window's size to reflect the new scale factor. If a window hasn't
//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event,
//! then its scale factor can be found by calling [window.scale_factor()].
//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
//! monitor, or if the user changes their DPI settings. This gives you a chance to rescale your
//! application's UI elements and adjust how the platform changes the window's size to reflect the new
//! scale factor. If a window hasn't received a [`ScaleFactorChanged`] event, then its scale factor
//! can be found by calling [`window.scale_factor()`].
//!
//! ## How is the scale factor calculated?
//!
@@ -77,7 +77,7 @@
//! currently uses a three-pronged approach:
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
//! + If not present, use the value set in `Xft.dpi` in Xresources.
//! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
//!
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
@@ -93,9 +93,11 @@
//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
//! both the screen scaling and the browser zoom level and can go below `1.0`.
//!
//!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
//! [window.scale_factor()]: crate::window::Window::scale_factor
//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
//! [`window.scale_factor()`]: crate::window::Window::scale_factor
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/

View File

@@ -1,10 +1,10 @@
//! The `Event` enum and assorted supporting types.
//! The [`Event`] enum and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get
//! These are sent to the closure given to [`EventLoop::run(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level documentation.
//!
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this:
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
//!
//! ```rust,ignore
//! let mut control_flow = ControlFlow::Poll;
@@ -29,13 +29,16 @@
//! event_handler(LoopDestroyed, ..., &mut control_flow);
//! ```
//!
//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order.
//!
//! [event_loop_run]: crate::event_loop::EventLoop::run
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use instant::Instant;
use std::path::PathBuf;
#[cfg(doc)]
use crate::window::Window;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
@@ -71,9 +74,107 @@ pub enum Event<'a, T: 'static> {
UserEvent(T),
/// Emitted when the application has been suspended.
///
/// # Portability
///
/// Not all platforms support the notion of suspending applications, and there may be no
/// technical way to guarantee being able to emit a `Suspended` event if the OS has
/// no formal application lifecycle (currently only Android and iOS do). For this reason,
/// Winit does not currently try to emit pseudo `Suspended` events before the application
/// quits on platforms without an application lifecycle.
///
/// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally
/// driven by multiple platform-specific events, and that there may be subtle differences across
/// platforms with how these internal events are delivered, it's recommended that applications
/// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events.
///
/// Also see [`Resumed`] notes.
///
/// ## Android
///
/// On Android, the `Suspended` event is only sent when the application's associated
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
/// lifecycle event but there may technically be a discrepancy.
///
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
///
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
///
/// After being `Suspended` on Android applications must drop all render surfaces before
/// the event callback completes, which may be re-created when the application is next [`Resumed`].
///
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// ## iOS
///
/// On iOS, the `Suspended` event is currently emitted in response to an
/// [`applicationWillResignActive`] callback which means that the application is
/// about to transition from the active to inactive state (according to the
/// [iOS application lifecycle]).
///
/// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
///
/// [`Resumed`]: Self::Resumed
Suspended,
/// Emitted when the application has been resumed.
///
/// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a
/// formal suspend/resume lifecycle. For systems without a standard suspend/resume lifecycle
/// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init]
/// event.
///
/// # Portability
///
/// It's recommended that applications should only initialize their graphics context and create
/// a window after they have received their first `Resumed` event. Some systems
/// (specifically Android) won't allow applications to create a render surface until they are
/// resumed.
///
/// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally
/// driven by multiple platform-specific events, and that there may be subtle differences across
/// platforms with how these internal events are delivered, it's recommended that applications
/// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events.
///
/// Also see [`Suspended`] notes.
///
/// ## Android
///
/// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is
/// expected to closely correlate with the [`onResume`] lifecycle event but there may technically
/// be a discrepancy.
///
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
///
/// Applications that need to run on Android must wait until they have been `Resumed`
/// before they will be able to create a render surface (such as an `EGLSurface`,
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a
/// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their
/// render surfaces are invalid and should be dropped.
///
/// Also see [`Suspended`] notes.
///
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// ## iOS
///
/// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`]
/// callback which means the application is "active" (according to the
/// [iOS application lifecycle]).
///
/// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
///
/// [`Suspended`]: Self::Suspended
Resumed,
/// Emitted when all of the event loop's input events have been processed and redraw processing
@@ -88,27 +189,30 @@ pub enum Event<'a, T: 'static> {
/// can render here unconditionally for simplicity.
MainEventsCleared,
/// Emitted after `MainEventsCleared` when a window should be redrawn.
/// Emitted after [`MainEventsCleared`] when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via
/// [`Window::request_redraw`](crate::window::Window::request_redraw).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
///
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
/// into a single event, to help avoid duplicating rendering work.
///
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
/// something changes, like most non-game GUIs.
///
/// [`MainEventsCleared`]: Self::MainEventsCleared
RedrawRequested(WindowId),
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
/// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to
/// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
/// immediately after `MainEventsCleared`.
///
/// This event is useful for doing any cleanup or bookkeeping work after all the rendering
/// tasks have been completed.
///
/// [`RedrawRequested`]: Self::RedrawRequested
RedrawEventsCleared,
/// Emitted when the event loop is being shut down.
@@ -183,9 +287,11 @@ impl<'a, T> Event<'a, T> {
/// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StartCause {
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
/// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the
/// moment the timeout was requested and the requested resume time. The actual resume time is
/// guaranteed to be equal to or after the requested resume time.
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
ResumeTimeReached {
start: Instant,
requested_resume: Instant,
@@ -199,14 +305,16 @@ pub enum StartCause {
},
/// Sent if the event loop is being resumed after the loop's control flow was set to
/// `ControlFlow::Poll`.
/// [`ControlFlow::Poll`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
Poll,
/// Sent once, immediately after `run` is called. Indicates that the loop was just initialized.
Init,
}
/// Describes an event from a `Window`.
/// Describes an event from a [`Window`].
#[derive(Debug, PartialEq)]
pub enum WindowEvent<'a> {
/// The size of the window has changed. Contains the client area's new dimensions.
@@ -240,6 +348,8 @@ pub enum WindowEvent<'a> {
HoveredFileCancelled,
/// The window received a unicode character.
///
/// See also the [`Ime`](Self::Ime) event for more complex character sequences.
ReceivedCharacter(char),
/// The window gained or lost focus.
@@ -265,11 +375,21 @@ pub enum WindowEvent<'a> {
/// The keyboard modifiers have changed.
///
/// Platform-specific behavior:
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
/// ## Platform-specific
///
/// - **Web:** This API is currently unimplemented on the web. This isn't by design - it's an
/// issue, and it should get fixed - but it's the current state of the API.
ModifiersChanged(ModifiersState),
/// An event from an input method.
///
/// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`].
///
/// ## Platform-specific
///
/// - **iOS / Android / Web:** Unsupported.
Ime(Ime),
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
@@ -350,8 +470,19 @@ pub enum WindowEvent<'a> {
/// Applications might wish to react to this to change the theme of the content of the window
/// when the system changes the window theme.
///
/// ## Platform-specific
///
/// At the moment this is only supported on Windows.
ThemeChanged(Theme),
/// The window has been occluded (completely hidden from view).
///
/// This is different to window visibility as it depends on whether the window is closed,
/// minimised, set invisible, or fully occluded by another window.
///
/// Platform-specific behavior:
/// - **iOS / Android / Web / Wayland / Windows:** Unsupported.
Occluded(bool),
}
impl Clone for WindowEvent<'static> {
@@ -376,7 +507,7 @@ impl Clone for WindowEvent<'static> {
input: *input,
is_synthetic: *is_synthetic,
},
Ime(preedit_state) => Ime(preedit_state.clone()),
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
#[allow(deprecated)]
CursorMoved {
@@ -441,6 +572,7 @@ impl Clone for WindowEvent<'static> {
ScaleFactorChanged { .. } => {
unreachable!("Static event can't be about scale factor changing")
}
Occluded(occluded) => Occluded(*occluded),
};
}
}
@@ -468,6 +600,7 @@ impl<'a> WindowEvent<'a> {
is_synthetic,
}),
ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)),
Ime(event) => Some(Ime(event)),
#[allow(deprecated)]
CursorMoved {
device_id,
@@ -525,6 +658,7 @@ impl<'a> WindowEvent<'a> {
Touch(touch) => Some(Touch(touch)),
ThemeChanged(theme) => Some(ThemeChanged(theme)),
ScaleFactorChanged { .. } => None,
Occluded(occluded) => Some(Occluded(occluded)),
}
}
}
@@ -538,7 +672,7 @@ impl<'a> WindowEvent<'a> {
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing.
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
///
@@ -567,7 +701,7 @@ pub enum DeviceEvent {
/// Change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
/// This represents raw, unfiltered physical motion. Not to be confused with [`WindowEvent::CursorMoved`].
MouseMotion {
/// (x, y) change in position in unspecified units.
///
@@ -580,7 +714,7 @@ pub enum DeviceEvent {
delta: MouseScrollDelta,
},
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
/// that winit supports on this platform, including mouse devices. If the device is a mouse
/// device then this will be reported alongside the MouseMotion event.
Motion {
@@ -627,6 +761,72 @@ pub struct KeyboardInput {
pub modifiers: ModifiersState,
}
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
///
/// This is also called a "composition event".
///
/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::ReceivedCharacter`].
/// However, one couldn't possibly have a key for every single unicode character that the user might want to type
/// - so the solution operating systems employ is to allow the user to type these using _a sequence of keypresses_ instead.
///
/// A prominent example of this is accents - many keyboard layouts allow you to first click the "accent key", and then
/// the character you want to apply the accent to. This will generate the following event sequence:
/// ```ignore
/// // Press "`" key
/// Ime::Preedit("`", Some(0), Some(0))
/// // Press "E" key
/// Ime::Commit("é")
/// ```
///
/// Additionally, certain input devices are configured to display a candidate box that allow the user to select the
/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_position`].)
///
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keybaord the following event
/// sequence could be obtained:
/// ```ignore
/// // Press "A" key
/// Ime::Preedit("a", Some(1), Some(1))
/// // Press "B" key
/// Ime::Preedit("a b", Some(3), Some(3))
/// // Press left arrow key
/// Ime::Preedit("a b", Some(1), Some(1))
/// // Press space key
/// Ime::Preedit("啊b", Some(3), Some(3))
/// // Press space key
/// Ime::Commit("啊不")
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Ime {
/// Notifies when the IME was enabled.
///
/// After getting this event you could receive [`Preedit`](Self::Preedit) and
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests
/// like [`Window::set_ime_position`].
Enabled,
/// Notifies when a new composing text should be set at the cursor position.
///
/// The value represents a pair of the preedit string and the cursor begin position and end
/// position. When it's `None`, the cursor should be hidden.
///
/// The cursor position is byte-wise indexed.
Preedit(String, Option<(usize, usize)>),
/// Notifies when text should be inserted into the editor widget.
///
/// Any pending [`Preedit`](Self::Preedit) must be cleared.
Commit(String),
/// Notifies when the IME was disabled.
///
/// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You can
/// also stop issuing IME related requests like [`Window::set_ime_position`] and clear pending
/// preedit text.
Disabled,
}
/// Describes touch-screen input state.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -639,18 +839,18 @@ pub enum TouchPhase {
/// Represents a touch event
///
/// Every time the user touches the screen, a new `Start` event with an unique
/// identifier for the finger is generated. When the finger is lifted, an `End`
/// Every time the user touches the screen, a new [`TouchPhase::Started`] event with an unique
/// identifier for the finger is generated. When the finger is lifted, an [`TouchPhase::Ended`]
/// event is generated with the same finger id.
///
/// After a `Start` event has been emitted, there may be zero or more `Move`
/// After a `Started` event has been emitted, there may be zero or more `Move`
/// events when the finger is moved or the touch pressure changes.
///
/// The finger id may be reused by the system after an `End` event. The user
/// should assume that a new `Start` event received with the same id has nothing
/// The finger id may be reused by the system after an `Ended` event. The user
/// should assume that a new `Started` event received with the same id has nothing
/// to do with the old finger and is a new finger.
///
/// A `Cancelled` event is emitted when the system has canceled tracking this
/// A [`TouchPhase::Cancelled`] event is emitted when the system has canceled tracking this
/// touch, such as when the window loses focus, or on iOS if the user moves the
/// device against their face.
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -704,8 +904,9 @@ pub enum Force {
impl Force {
/// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
///
/// Instead of normalizing the force, you should prefer to handle
/// `Force::Calibrated` so that the amount of force the user has to apply is
/// [`Force::Calibrated`] so that the amount of force the user has to apply is
/// consistent across devices.
pub fn normalized(&self) -> f64 {
match self {
@@ -759,15 +960,23 @@ pub enum MouseScrollDelta {
/// Amount in lines or rows to scroll in the horizontal
/// and vertical directions.
///
/// Positive values indicate movement forward
/// (away from the user) or rightwards.
/// Positive values indicate that the content that is being scrolled should move
/// right and down (revealing more content left and up).
LineDelta(f32, f32),
/// Amount in pixels to scroll in the horizontal and
/// vertical direction.
///
/// Scroll events are expressed as a PixelDelta if
/// Scroll events are expressed as a `PixelDelta` if
/// supported by the device (eg. a touchpad) and
/// platform.
///
/// Positive values indicate that the content being scrolled should
/// move right/down.
///
/// For a 'natural scrolling' touch pad (that acts like a touch screen)
/// this means moving your fingers right and down should give positive values,
/// and move the content right and down (to reveal more things left and up).
PixelDelta(PhysicalPosition<f64>),
}

View File

@@ -1,48 +1,117 @@
//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`.
//! The [`EventLoop`] struct and assorted supporting types, including
//! [`ControlFlow`].
//!
//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy]
//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method.
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`send_event`](`EventLoopProxy::send_event`) method.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
//!
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
use instant::Instant;
use std::marker::PhantomData;
use std::ops::Deref;
use std::{error, fmt};
use instant::Instant;
use once_cell::sync::OnceCell;
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
///
/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()`
/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`]
/// initializes everything that will be required to create windows. For example on Linux creating
/// an event loop opens a connection to the X or Wayland server.
///
/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs.
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
///
/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the
/// [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop<T>,
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
/// Target that associates windows with an `EventLoop`.
/// Target that associates windows with an [`EventLoop`].
///
/// This type exists to allow you to create new windows while Winit executes
/// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
/// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for
/// EventLoop<T>`), so functions that take this as a parameter can also take
/// `&EventLoop`.
pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
/// Object that allows building the event loop.
///
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
#[derive(Default)]
pub struct EventLoopBuilder<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
}
impl EventLoopBuilder<()> {
/// Start building a new event loop.
#[inline]
pub fn new() -> Self {
Self::with_user_event()
}
}
impl<T> EventLoopBuilder<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
#[inline]
pub fn with_user_event() -> Self {
Self {
platform_specific: Default::default(),
_p: PhantomData,
}
}
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
/// and only once per application.***
///
/// Attempting to create the event loop on a different thread, or multiple event loops in
/// the same application, will panic. This restriction isn't
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
/// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
/// in the relevant [`platform`] module if the target platform supports creating an event loop on
/// any thread.
///
/// Calling this function will result in display backend initialisation.
///
/// ## Platform-specific
///
/// - **Linux:** Backend type can be controlled using an environment variable
/// `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.
///
/// [`platform`]: crate::platform
#[inline]
pub fn build(&mut self) -> EventLoop<T> {
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
if EVENT_LOOP_CREATED.set(()).is_err() {
panic!("Creating EventLoop multiple times is not supported.");
}
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
EventLoop {
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
_marker: PhantomData,
}
}
}
impl<T> fmt::Debug for EventLoop<T> {
@@ -57,24 +126,28 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
}
}
/// Set by the user callback given to the `EventLoop::run` method.
/// Set by the user callback given to the [`EventLoop::run`] method.
///
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared]
/// is emitted. Defaults to `Poll`.
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
///
/// Defaults to [`Poll`].
///
/// ## Persistency
/// Almost every change is persistent between multiple calls to the event loop closure within a
/// given run loop. The only exception to this is `Exit` which, once set, cannot be unset. Changes
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
/// the control flow to `Poll`.
///
/// [events_cleared]: crate::event::Event::RedrawEventsCleared
/// Almost every change is persistent between multiple calls to the event loop closure within a
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
/// reset the control flow to [`Poll`].
///
/// [`ExitWithCode`]: Self::ExitWithCode
/// [`Poll`]: Self::Poll
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
///
/// ## 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
@@ -84,56 +157,102 @@ pub enum ControlFlow {
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the display's
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
/// to reduce odds of missed frames.
///
/// [`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 `Exit`, and any future attempts to do so will result
/// in the `control_flow` parameter being reset to `Exit`.
Exit,
/// 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`.
///
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
/// with exit code 0.
///
/// ## Platform-specific
///
/// - **Android / iOS / WASM:** The supplied exit code is unused.
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
/// [`std::process::exit`].
///
/// [`LoopDestroyed`]: Event::LoopDestroyed
/// [`Exit`]: ControlFlow::Exit
ExitWithCode(i32),
}
impl ControlFlow {
/// Alias for [`ExitWithCode`]`(0)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
#[allow(non_upper_case_globals)]
pub const Exit: Self = Self::ExitWithCode(0);
/// Sets this to [`Poll`].
///
/// [`Poll`]: Self::Poll
pub fn set_poll(&mut self) {
*self = Self::Poll;
}
/// Sets this to [`Wait`].
///
/// [`Wait`]: Self::Wait
pub fn set_wait(&mut self) {
*self = Self::Wait;
}
/// Sets this to [`WaitUntil`]`(instant)`.
///
/// [`WaitUntil`]: Self::WaitUntil
pub fn set_wait_until(&mut self, instant: Instant) {
*self = Self::WaitUntil(instant);
}
/// Sets this to [`ExitWithCode`]`(code)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
pub fn set_exit_with_code(&mut self, code: i32) {
*self = Self::ExitWithCode(code);
}
/// Sets this to [`Exit`].
///
/// [`Exit`]: Self::Exit
pub fn set_exit(&mut self) {
*self = Self::Exit;
}
}
impl Default for ControlFlow {
#[inline(always)]
fn default() -> ControlFlow {
ControlFlow::Poll
fn default() -> Self {
Self::Poll
}
}
impl EventLoop<()> {
/// Builds a new event loop with a `()` as the user event type.
/// Alias for [`EventLoopBuilder::new().build()`].
///
/// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
/// Attempting to create the event loop on a different thread will panic. This restriction isn't
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
/// porting to platforms that require it. `EventLoopExt::new_any_thread` functions are exposed
/// in the relevant `platform` module if the target platform supports creating an event loop on
/// any thread.
///
/// Usage will result in display backend initialisation, this can be controlled on linux
/// using an environment variable `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 it fails will
/// fallback on x11. If this variable is set with any other value, winit will panic.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
#[inline]
pub fn new() -> EventLoop<()> {
EventLoop::<()>::with_user_event()
EventLoopBuilder::new().build()
}
}
impl Default for EventLoop<()> {
fn default() -> Self {
Self::new()
}
}
impl<T> EventLoop<T> {
/// Builds a new event loop.
///
/// All caveats documented in [`EventLoop::new`] apply to this function.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
pub fn with_user_event() -> EventLoop<T> {
EventLoop {
event_loop: platform_impl::EventLoop::new(),
_marker: ::std::marker::PhantomData,
}
EventLoopBuilder::<T>::with_user_event().build()
}
/// Hijacks the calling thread and initializes the winit event loop with the provided
@@ -145,6 +264,11 @@ impl<T> EventLoop<T> {
///
/// Any values not passed to this function will *not* be dropped.
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
/// disconnects.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
@@ -154,7 +278,7 @@ impl<T> EventLoop<T> {
self.event_loop.run(event_handler)
}
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(),
@@ -190,9 +314,38 @@ impl<T> EventLoopWindowTarget<T> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.p.primary_monitor()
}
/// Change [`DeviceEvent`] filter mode.
///
/// 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.
///
/// ## Platform-specific
///
/// - **Wayland / Windows / macOS / iOS / Android / Web:** 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"
))]
self.p.set_device_event_filter(_filter);
}
}
/// Used to send custom events to `EventLoop`.
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.p.raw_display_handle()
}
}
/// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
}
@@ -206,11 +359,13 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
}
impl<T: 'static> EventLoopProxy<T> {
/// Send an event to the `EventLoop` from which this proxy was created. This emits a
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
/// function.
///
/// Returns an `Err` if the associated `EventLoop` no longer exists.
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
///
/// [`UserEvent(event)`]: Event::UserEvent
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.event_loop_proxy.send_event(event)
}
@@ -222,8 +377,10 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
}
}
/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
/// no longer exists. Contains the original event given to `send_event`.
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
/// no longer exists.
///
/// Contains the original event given to [`EventLoopProxy::send_event`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
@@ -234,3 +391,20 @@ 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.
Always,
/// Filter out device events while the window is not focused.
Unfocused,
/// Report all device events regardless of window focus.
Never,
}
impl Default for DeviceEventFilter {
fn default() -> Self {
Self::Unfocused
}
}

View File

@@ -13,7 +13,7 @@ pub(crate) struct Pixel {
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
#[derive(Debug)]
/// An error produced when using `Icon::from_rgba` with invalid arguments.
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
pub enum BadIcon {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
@@ -73,10 +73,6 @@ mod constructors {
use super::*;
impl RgbaIcon {
/// Creates an `Icon` from 32bpp RGBA data.
///
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 {
@@ -123,7 +119,7 @@ impl fmt::Debug for Icon {
}
impl Icon {
/// Creates an `Icon` from 32bpp RGBA data.
/// Creates an icon from 32bpp RGBA data.
///
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.

View File

@@ -31,8 +31,8 @@
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the
//! entire program terminates.
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
@@ -44,7 +44,7 @@
//! ```no_run
//! use winit::{
//! event::{Event, WindowEvent},
//! event_loop::{ControlFlow, EventLoop},
//! event_loop::EventLoop,
//! window::WindowBuilder,
//! };
//!
@@ -54,12 +54,12 @@
//! event_loop.run(move |event, _, control_flow| {
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! *control_flow = ControlFlow::Poll;
//! control_flow.set_poll();
//!
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! *control_flow = ControlFlow::Wait;
//! control_flow.set_wait();
//!
//! match event {
//! Event::WindowEvent {
@@ -67,7 +67,7 @@
//! ..
//! } => {
//! println!("The close button was pressed; stopping");
//! *control_flow = ControlFlow::Exit
//! control_flow.set_exit();
//! },
//! Event::MainEventsCleared => {
//! // Application update code.
@@ -98,9 +98,9 @@
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
//! retrieve the raw handle of the window (see the [`platform`] module and/or the
//! [`raw_window_handle`] method), which in turn allows you to create an
//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//!
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
@@ -114,6 +114,7 @@
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder
@@ -128,13 +129,14 @@
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
#![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
#![allow(clippy::missing_safety_doc)]
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
#[allow(unused_imports)]
#[macro_use]
extern crate log;

View File

@@ -1,14 +1,10 @@
//! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle]
//! type. This is retrieved from one of the following methods, which return an iterator of
//! [`MonitorHandle`][monitor_handle]:
//! - [`EventLoopWindowTarget::available_monitors`][loop_get]
//! - [`Window::available_monitors`][window_get].
//!
//! [monitor_handle]: crate::monitor::MonitorHandle
//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors
//! [window_get]: crate::window::Window::available_monitors
//! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`EventLoopWindowTarget::available_monitors`](crate::event_loop::EventLoopWindowTarget::available_monitors).
//! - [`Window::available_monitors`](crate::window::Window::available_monitors).
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
@@ -16,10 +12,7 @@ use crate::{
/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with:
/// - [`MonitorHandle::video_modes`][monitor_get].
///
/// [monitor_get]: crate::monitor::MonitorHandle::video_modes
/// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) video_mode: platform_impl::VideoMode,
@@ -46,8 +39,8 @@ impl Ord for VideoMode {
self.monitor().cmp(&other.monitor()).then(
size.cmp(&other_size)
.then(
self.refresh_rate()
.cmp(&other.refresh_rate())
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
@@ -75,12 +68,10 @@ impl VideoMode {
self.video_mode.bit_depth()
}
/// Returns the refresh rate of this video mode. **Note**: the returned
/// refresh rate is an integer approximation, and you shouldn't rely on this
/// value to be exact.
/// Returns the refresh rate of this video mode in mHz.
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.video_mode.refresh_rate()
pub fn refresh_rate_millihertz(&self) -> u32 {
self.video_mode.refresh_rate_millihertz()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
@@ -95,10 +86,10 @@ impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} @ {} Hz ({} bpp)",
"{}x{} @ {} mHz ({} bpp)",
self.size().width,
self.size().height,
self.refresh_rate(),
self.refresh_rate_millihertz(),
self.bit_depth()
)
}
@@ -148,6 +139,15 @@ impl MonitorHandle {
self.inner.position()
}
/// The monitor refresh rate used by the system.
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to
/// enter fullscreen should be used instead.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz()
}
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](crate::dpi) module for more information.

View File

@@ -7,15 +7,15 @@ use crate::{
use ndk::configuration::Configuration;
use ndk_glue::Rect;
/// Additional methods on `EventLoop` that are specific to Android.
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
/// Additional methods on `EventLoopWindowTarget` that are specific to Android.
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Android.
pub trait EventLoopWindowTargetExtAndroid {}
/// Additional methods on `Window` that are specific to Android.
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;
@@ -34,7 +34,7 @@ impl WindowExtAndroid for Window {
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
/// Additional methods on `WindowBuilder` that are specific to Android.
/// Additional methods on [`WindowBuilder`] that are specific to Android.
pub trait WindowBuilderExtAndroid {}
impl WindowBuilderExtAndroid for WindowBuilder {}

View File

@@ -4,22 +4,21 @@ use std::os::raw::c_void;
use crate::{
dpi::LogicalSize,
event_loop::{EventLoop, EventLoopWindowTarget},
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
platform_impl::get_aux_state_mut,
window::{Window, WindowBuilder},
};
/// Additional methods on `Window` that are specific to MacOS.
/// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
fn ns_window(&self) -> *mut c_void;
/// Returns a pointer to the cocoa `NSView` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
fn ns_view(&self) -> *mut c_void;
/// Returns whether or not the window is in simple fullscreen mode.
@@ -74,7 +73,7 @@ impl WindowExtMacOS for Window {
}
/// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`.
Regular,
@@ -90,16 +89,14 @@ impl Default for ActivationPolicy {
}
}
/// Additional methods on `WindowBuilder` that are specific to MacOS.
/// Additional methods on [`WindowBuilder`] that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method
/// on the base `WindowBuilder`:
///
/// - `with_titlebar_transparent`
/// - `with_title_hidden`
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
/// - `with_titlebar_transparent`
/// - `with_title_hidden`
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
pub trait WindowBuilderExtMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool)
@@ -162,7 +159,7 @@ impl WindowBuilderExtMacOS for WindowBuilder {
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
self.platform_specific.resize_increments = Some(increments.into());
self.platform_specific.resize_increments = Some(increments);
self
}
@@ -179,40 +176,67 @@ impl WindowBuilderExtMacOS for WindowBuilder {
}
}
pub trait EventLoopExtMacOS {
/// Sets the activation policy for the application. It is set to
/// `NSApplicationActivationPolicyRegular` by default.
pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application.
///
/// This function only takes effect if it's called before calling [`run`](crate::event_loop::EventLoop::run) or
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy);
/// It is set to [`ActivationPolicy::Regular`] by default.
///
/// # Example
///
/// Set the activation policy to "accessory".
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy};
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "macos")]
/// builder.with_activation_policy(ActivationPolicy::Accessory);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
/// Used to prevent a default menubar menu from getting created
/// Used to control whether a default menubar menu is created.
///
/// The default menu creation is enabled by default.
/// Menu creation is enabled by default.
///
/// This function only takes effect if it's called before calling
/// [`run`](crate::event_loop::EventLoop::run) or
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
fn enable_default_menu_creation(&mut self, enable: bool);
/// # Example
///
/// Disable creating a default menubar.
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "macos")]
/// builder.with_default_menu(false);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
}
impl<T> EventLoopExtMacOS for EventLoop<T> {
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline]
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
unsafe {
get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy;
}
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy;
self
}
#[inline]
fn enable_default_menu_creation(&mut self, enable: bool) {
unsafe {
get_aux_state_mut(&**self.event_loop.delegate).create_default_menu = enable;
}
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
self.platform_specific.default_menu = enable;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to MacOS.
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
@@ -231,7 +255,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
}
}
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
/// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS.
pub trait EventLoopWindowTargetExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
fn hide_application(&self);

View File

@@ -19,7 +19,7 @@ pub mod android;
pub mod ios;
pub mod macos;
pub mod unix;
pub mod web;
pub mod windows;
pub mod run_return;
pub mod web;

View File

@@ -14,17 +14,18 @@ use crate::{
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
/// Additional methods on `EventLoop` to return control flow to the caller.
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunReturn {
/// A type provided by the user that can be passed through `Event::UserEvent`.
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent;
/// Initializes the `winit` event loop.
///
/// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns
/// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`.
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
/// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`].
///
/// # Caveats
///
/// Despite its appearance at first glance, this is *not* a perfect replacement for
/// `poll_events`. For example, this function will not return on Windows or macOS while a
/// window is getting resized, resulting in all application logic outside of the
@@ -33,7 +34,12 @@ pub trait EventLoopExtRunReturn {
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
///
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
fn run_return<F>(&mut self, event_handler: F)
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** This function returns `1` upon disconnection from
/// the display server.
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,
@@ -45,7 +51,7 @@ pub trait EventLoopExtRunReturn {
impl<T> EventLoopExtRunReturn for EventLoop<T> {
type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F)
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,

View File

@@ -11,7 +11,7 @@ use std::os::raw;
use std::{ptr, sync::Arc};
use crate::{
event_loop::{EventLoop, EventLoopWindowTarget},
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
@@ -19,9 +19,9 @@ use crate::{
#[cfg(feature = "x11")]
use crate::dpi::Size;
#[cfg(feature = "x11")]
use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection};
use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS};
use crate::platform_impl::{
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
@@ -32,13 +32,41 @@ pub use crate::platform_impl::x11;
#[cfg(feature = "x11")]
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
#[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.
/// True if the [`EventLoopWindowTarget`] uses Wayland.
#[cfg(feature = "wayland")]
fn is_wayland(&self) -> bool;
/// True if the `EventLoopWindowTarget` uses X11.
/// True if the [`EventLoopWindowTarget`] uses X11.
#[cfg(feature = "x11")]
fn is_x11(&self) -> bool;
@@ -47,11 +75,13 @@ pub trait EventLoopWindowTargetExtUnix {
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// `EventLoopWindowTarget`.
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
/// 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.
/// 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>;
}
@@ -70,7 +100,6 @@ impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
}
#[inline]
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.p {
@@ -93,106 +122,48 @@ impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
}
}
/// Additional methods on `EventLoop` that are specific to Unix.
pub trait EventLoopExtUnix {
/// Builds a new `EventLoop` that is forced to use X11.
///
/// # Panics
///
/// If called outside the main thread. To initialize an X11 event loop outside
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
/// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
pub trait EventLoopBuilderExtUnix {
/// Force using X11.
#[cfg(feature = "x11")]
fn new_x11() -> Result<Self, XNotSupported>
where
Self: Sized;
fn with_x11(&mut self) -> &mut Self;
/// Builds a new `EventLoop` that is forced to use Wayland.
///
/// # Panics
///
/// If called outside the main thread. To initialize a Wayland event loop outside
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
/// Force using Wayland.
#[cfg(feature = "wayland")]
fn new_wayland() -> Self
where
Self: Sized;
fn with_wayland(&mut self) -> &mut Self;
/// Builds a new `EventLoop` on any thread.
/// Whether to allow the event loop to be created off of the main thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
fn new_any_thread() -> Self
where
Self: Sized;
/// Builds a new X11 `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
#[cfg(feature = "x11")]
fn new_x11_any_thread() -> Result<Self, XNotSupported>
where
Self: Sized;
/// Builds a new Wayland `EventLoop` on any thread.
///
/// This method bypasses the cross-platform compatibility requirement
/// that `EventLoop` be created on the main thread.
#[cfg(feature = "wayland")]
fn new_wayland_any_thread() -> Self
where
Self: Sized;
/// 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;
}
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> {
EventLoop {
event_loop,
_marker: std::marker::PhantomData,
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
}
}
impl<T> EventLoopExtUnix for EventLoop<T> {
#[inline]
fn new_any_thread() -> Self {
wrap_ev(LinuxEventLoop::new_any_thread())
}
#[inline]
#[cfg(feature = "x11")]
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
}
#[inline]
#[cfg(feature = "wayland")]
fn new_wayland_any_thread() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland_any_thread()
// TODO: propagate
.expect("failed to open Wayland connection"),
)
}
#[inline]
#[cfg(feature = "x11")]
fn new_x11() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11().map(wrap_ev)
}
#[inline]
#[cfg(feature = "wayland")]
fn new_wayland() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland()
// TODO: propagate
.expect("failed to open Wayland connection"),
)
}
}
/// Additional methods on `Window` that are specific to Unix.
/// 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 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")]
@@ -202,7 +173,7 @@ pub trait WindowExtUnix {
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
fn xlib_display(&self) -> Option<*mut raw::c_void>;
@@ -217,7 +188,7 @@ pub trait WindowExtUnix {
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
@@ -225,7 +196,7 @@ pub trait WindowExtUnix {
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
/// The pointer will become invalid when the [`Window`] is destroyed.
#[cfg(feature = "wayland")]
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
@@ -233,16 +204,23 @@ pub trait WindowExtUnix {
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
/// 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.
/// Always return `true`.
#[deprecated]
fn is_ready(&self) -> bool;
}
@@ -279,7 +257,6 @@ impl WindowExtUnix for Window {
}
#[inline]
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
@@ -319,45 +296,91 @@ impl WindowExtUnix for Window {
}
}
#[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.
/// 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 `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
#[cfg(feature = "x11")]
fn with_class(self, class: String, instance: 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 resize increment hint. Only implemented on X11.
#[cfg(feature = "x11")]
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
/// Build window with base size hint. Only implemented on X11.
#[cfg(feature = "x11")]
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
/// Build window with a given application ID. It should match the `.desktop` file distributed with
/// your program. Only relevant on Wayland.
/// 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_app_id(self, app_id: String) -> Self;
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 {
@@ -379,9 +402,8 @@ impl WindowBuilderExtUnix for WindowBuilder {
}
#[inline]
#[cfg(feature = "x11")]
fn with_class(mut self, instance: String, class: String) -> Self {
self.platform_specific.class = Some((instance, class));
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
}
@@ -406,6 +428,13 @@ impl WindowBuilderExtUnix for WindowBuilder {
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 {
@@ -419,13 +448,6 @@ impl WindowBuilderExtUnix for WindowBuilder {
self.platform_specific.base_size = Some(base_size.into());
self
}
#[inline]
#[cfg(feature = "wayland")]
fn with_app_id(mut self, app_id: String) -> Self {
self.platform_specific.app_id = Some(app_id);
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Linux.

View File

@@ -1,10 +1,14 @@
#![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
//! 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
//! to provide your own canvas.
use crate::event::Event;
use crate::event_loop::ControlFlow;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::window::WindowBuilder;
use web_sys::HtmlCanvasElement;
@@ -18,6 +22,17 @@ pub trait WindowExtWebSys {
pub trait WindowBuilderExtWebSys {
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
/// when appropriate.
///
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
/// the default behavior of scrolling the page.
fn with_prevent_default(self, prevent_default: bool) -> Self;
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
/// canvas keyboard events.
fn with_focusable(self, focusable: bool) -> Self;
}
impl WindowBuilderExtWebSys for WindowBuilder {
@@ -26,4 +41,51 @@ impl WindowBuilderExtWebSys for WindowBuilder {
self
}
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.platform_specific.prevent_default = prevent_default;
self
}
fn with_focusable(mut self, focusable: bool) -> Self {
self.platform_specific.focusable = focusable;
self
}
}
/// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent;
/// Initializes the winit event loop.
///
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
/// satisfy its `!` return type.
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
type UserEvent = T;
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
{
self.event_loop.spawn(event_handler)
}
}

View File

@@ -1,81 +1,124 @@
#![cfg(target_os = "windows")]
use std::os::raw::c_void;
use std::path::Path;
use winapi::shared::minwindef::WORD;
use winapi::shared::windef::{HMENU, HWND};
use std::{ffi::c_void, path::Path};
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event_loop::EventLoop,
event_loop::EventLoopBuilder,
monitor::MonitorHandle,
platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon},
platform_impl::{Parent, WinIcon},
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
};
/// Window Handle type used by Win32 API
pub type HWND = isize;
/// Menu Handle type used by Win32 API
pub type HMENU = isize;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = isize;
/// Instance Handle type used by Win32 API
pub type HINSTANCE = isize;
/// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopExtWindows {
/// Creates an event loop off of the main thread.
pub trait EventLoopBuilderExtWindows {
/// 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.
///
/// # `Window` caveats
///
/// Note that any `Window` created on the new thread will be destroyed when the thread
/// terminates. Attempting to use a `Window` after its parent thread terminates has
/// unspecified, although explicitly not undefined, behavior.
fn new_any_thread() -> Self
where
Self: Sized;
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
/// undesirable, you can create an `EventLoop` using this function instead.
fn new_dpi_unaware() -> Self
where
Self: Sized;
/// Creates a DPI-unaware event loop off of the main thread.
/// Whether to enable process-wide DPI awareness.
///
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here.
fn new_dpi_unaware_any_thread() -> Self
/// By default, `winit` will attempt to enable process-wide DPI awareness. If
/// that's undesirable, you can disable it with this function.
///
/// # Example
///
/// Disable process-wide DPI awareness.
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "windows")]
/// use winit::platform::windows::EventLoopBuilderExtWindows;
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "windows")]
/// builder.with_dpi_aware(false);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self;
/// A callback to be executed before dispatching a win32 message to the window procedure.
/// Return true to disable winit's internal message dispatching.
///
/// # Example
///
/// ```
/// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG};
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "windows")]
/// use winit::platform::windows::EventLoopBuilderExtWindows;
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "windows")]
/// builder.with_msg_hook(|msg|{
/// let msg = msg as *const MSG;
/// # let accels: Vec<ACCEL> = Vec::new();
/// let translated = unsafe {
/// TranslateAcceleratorW(
/// (*msg).hwnd,
/// CreateAcceleratorTableW(accels.as_ptr() as _, 1),
/// msg,
/// ) == 1
/// };
/// translated
/// });
/// ```
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
where
Self: Sized;
F: FnMut(*const c_void) -> bool + 'static;
}
impl<T> EventLoopExtWindows for EventLoop<T> {
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
#[inline]
fn new_any_thread() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_any_thread(),
_marker: ::std::marker::PhantomData,
}
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
#[inline]
fn new_dpi_unaware() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_dpi_unaware(),
_marker: ::std::marker::PhantomData,
}
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
self.platform_specific.dpi_aware = dpi_aware;
self
}
#[inline]
fn new_dpi_unaware_any_thread() -> Self {
EventLoop {
event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(),
_marker: ::std::marker::PhantomData,
}
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
where
F: FnMut(*const c_void) -> bool + 'static,
{
self.platform_specific.msg_hook = Some(Box::new(callback));
self
}
}
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows {
/// Returns the HINSTANCE of the window
fn hinstance(&self) -> *mut c_void;
fn hinstance(&self) -> HINSTANCE;
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
fn hwnd(&self) -> *mut c_void;
fn hwnd(&self) -> HWND;
/// Enables or disables mouse and keyboard input to the specified window.
///
@@ -97,17 +140,20 @@ pub trait WindowExtWindows {
/// 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);
}
impl WindowExtWindows for Window {
#[inline]
fn hinstance(&self) -> *mut c_void {
self.window.hinstance() as *mut _
fn hinstance(&self) -> HINSTANCE {
self.window.hinstance()
}
#[inline]
fn hwnd(&self) -> *mut c_void {
self.window.hwnd() as *mut _
fn hwnd(&self) -> HWND {
self.window.hwnd()
}
#[inline]
@@ -124,6 +170,11 @@ impl WindowExtWindows for Window {
fn theme(&self) -> Theme {
self.window.theme()
}
#[inline]
fn set_skip_taskbar(&self, skip: bool) {
self.window.set_skip_taskbar(skip)
}
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
@@ -151,10 +202,12 @@ pub trait WindowBuilderExtWindows {
///
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
///
/// The menu must have been manually created beforehand with [`winapi::um::winuser::CreateMenu`] or similar.
/// The menu must have been manually created beforehand with [`CreateMenu`] or similar.
///
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
///
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
fn with_menu(self, menu: HMENU) -> WindowBuilder;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
@@ -173,6 +226,9 @@ pub trait WindowBuilderExtWindows {
/// 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;
}
impl WindowBuilderExtWindows for WindowBuilder {
@@ -217,6 +273,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
self.platform_specific.preferred_theme = theme;
self
}
#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
self.platform_specific.skip_taskbar = skip;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Windows.
@@ -225,7 +287,7 @@ pub trait MonitorHandleExtWindows {
fn native_id(&self) -> String;
/// Returns the handle of the monitor - `HMONITOR`.
fn hmonitor(&self) -> *mut c_void;
fn hmonitor(&self) -> HMONITOR;
}
impl MonitorHandleExtWindows for MonitorHandle {
@@ -235,8 +297,8 @@ impl MonitorHandleExtWindows for MonitorHandle {
}
#[inline]
fn hmonitor(&self) -> *mut c_void {
self.inner.hmonitor() as *mut _
fn hmonitor(&self) -> HMONITOR {
self.inner.hmonitor()
}
}
@@ -274,7 +336,7 @@ pub trait IconExtWindows: Sized {
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
@@ -286,7 +348,7 @@ impl IconExtWindows for Icon {
Ok(Icon { inner: win_icon })
}
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}

View File

@@ -1,36 +1,45 @@
#![cfg(target_os = "android")]
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error, event,
event_loop::{self, ControlFlow},
monitor, window,
};
use ndk::{
configuration::Configuration,
event::{InputEvent, KeyAction, MotionAction},
looper::{ForeignLooper, Poll, ThreadLooper},
};
use ndk_glue::{Event, Rect};
use raw_window_handle::{AndroidNdkHandle, RawWindowHandle};
use std::{
collections::VecDeque,
sync::{Arc, Mutex, RwLock},
sync::{mpsc, RwLock},
time::{Duration, Instant},
};
lazy_static! {
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::from_asset_manager(
&ndk_glue::native_activity().asset_manager()
));
// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event
// contained in the `Option`. The event is moved outside of the `Option` replacing it with a
// `None`.
//
// This allows us to inject event into the event loop without going through `ndk-glue` and
// calling unsafe function that should only be called by Android.
static ref INTERNAL_EVENT: RwLock<Option<InternalEvent>> = RwLock::new(None);
}
use ndk::{
configuration::Configuration,
event::{InputEvent, KeyAction, Keycode, MotionAction},
looper::{ForeignLooper, Poll, ThreadLooper},
native_window::NativeWindow,
};
use ndk_glue::{Event, LockReadGuard, Rect};
use once_cell::sync::Lazy;
use raw_window_handle::{
AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, VirtualKeyCode},
event_loop::{self, ControlFlow},
monitor,
window::{self, CursorGrabMode},
};
static CONFIG: Lazy<RwLock<Configuration>> = Lazy::new(|| {
RwLock::new(Configuration::from_asset_manager(
#[allow(deprecated)] // TODO: rust-windowing/winit#2196
&ndk_glue::native_activity().asset_manager(),
))
});
// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event
// contained in the `Option`. The event is moved outside of the `Option` replacing it with a
// `None`.
//
// This allows us to inject event into the event loop without going through `ndk-glue` and
// calling unsafe function that should only be called by Android.
static INTERNAL_EVENT: Lazy<RwLock<Option<InternalEvent>>> = Lazy::new(|| RwLock::new(None));
enum InternalEvent {
RedrawRequested,
@@ -43,6 +52,170 @@ enum EventSource {
Internal(InternalEvent),
}
fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option<event::VirtualKeyCode> {
match keycode {
Keycode::A => Some(VirtualKeyCode::A),
Keycode::B => Some(VirtualKeyCode::B),
Keycode::C => Some(VirtualKeyCode::C),
Keycode::D => Some(VirtualKeyCode::D),
Keycode::E => Some(VirtualKeyCode::E),
Keycode::F => Some(VirtualKeyCode::F),
Keycode::G => Some(VirtualKeyCode::G),
Keycode::H => Some(VirtualKeyCode::H),
Keycode::I => Some(VirtualKeyCode::I),
Keycode::J => Some(VirtualKeyCode::J),
Keycode::K => Some(VirtualKeyCode::K),
Keycode::L => Some(VirtualKeyCode::L),
Keycode::M => Some(VirtualKeyCode::M),
Keycode::N => Some(VirtualKeyCode::N),
Keycode::O => Some(VirtualKeyCode::O),
Keycode::P => Some(VirtualKeyCode::P),
Keycode::Q => Some(VirtualKeyCode::Q),
Keycode::R => Some(VirtualKeyCode::R),
Keycode::S => Some(VirtualKeyCode::S),
Keycode::T => Some(VirtualKeyCode::T),
Keycode::U => Some(VirtualKeyCode::U),
Keycode::V => Some(VirtualKeyCode::V),
Keycode::W => Some(VirtualKeyCode::W),
Keycode::X => Some(VirtualKeyCode::X),
Keycode::Y => Some(VirtualKeyCode::Y),
Keycode::Z => Some(VirtualKeyCode::Z),
Keycode::Keycode0 => Some(VirtualKeyCode::Key0),
Keycode::Keycode1 => Some(VirtualKeyCode::Key1),
Keycode::Keycode2 => Some(VirtualKeyCode::Key2),
Keycode::Keycode3 => Some(VirtualKeyCode::Key3),
Keycode::Keycode4 => Some(VirtualKeyCode::Key4),
Keycode::Keycode5 => Some(VirtualKeyCode::Key5),
Keycode::Keycode6 => Some(VirtualKeyCode::Key6),
Keycode::Keycode7 => Some(VirtualKeyCode::Key7),
Keycode::Keycode8 => Some(VirtualKeyCode::Key8),
Keycode::Keycode9 => Some(VirtualKeyCode::Key9),
Keycode::Numpad0 => Some(VirtualKeyCode::Numpad0),
Keycode::Numpad1 => Some(VirtualKeyCode::Numpad1),
Keycode::Numpad2 => Some(VirtualKeyCode::Numpad2),
Keycode::Numpad3 => Some(VirtualKeyCode::Numpad3),
Keycode::Numpad4 => Some(VirtualKeyCode::Numpad4),
Keycode::Numpad5 => Some(VirtualKeyCode::Numpad5),
Keycode::Numpad6 => Some(VirtualKeyCode::Numpad6),
Keycode::Numpad7 => Some(VirtualKeyCode::Numpad7),
Keycode::Numpad8 => Some(VirtualKeyCode::Numpad8),
Keycode::Numpad9 => Some(VirtualKeyCode::Numpad9),
Keycode::NumpadAdd => Some(VirtualKeyCode::NumpadAdd),
Keycode::NumpadSubtract => Some(VirtualKeyCode::NumpadSubtract),
Keycode::NumpadMultiply => Some(VirtualKeyCode::NumpadMultiply),
Keycode::NumpadDivide => Some(VirtualKeyCode::NumpadDivide),
Keycode::NumpadEnter => Some(VirtualKeyCode::NumpadEnter),
Keycode::NumpadEquals => Some(VirtualKeyCode::NumpadEquals),
Keycode::NumpadComma => Some(VirtualKeyCode::NumpadComma),
Keycode::NumpadDot => Some(VirtualKeyCode::NumpadDecimal),
Keycode::NumLock => Some(VirtualKeyCode::Numlock),
Keycode::DpadLeft => Some(VirtualKeyCode::Left),
Keycode::DpadRight => Some(VirtualKeyCode::Right),
Keycode::DpadUp => Some(VirtualKeyCode::Up),
Keycode::DpadDown => Some(VirtualKeyCode::Down),
Keycode::F1 => Some(VirtualKeyCode::F1),
Keycode::F2 => Some(VirtualKeyCode::F2),
Keycode::F3 => Some(VirtualKeyCode::F3),
Keycode::F4 => Some(VirtualKeyCode::F4),
Keycode::F5 => Some(VirtualKeyCode::F5),
Keycode::F6 => Some(VirtualKeyCode::F6),
Keycode::F7 => Some(VirtualKeyCode::F7),
Keycode::F8 => Some(VirtualKeyCode::F8),
Keycode::F9 => Some(VirtualKeyCode::F9),
Keycode::F10 => Some(VirtualKeyCode::F10),
Keycode::F11 => Some(VirtualKeyCode::F11),
Keycode::F12 => Some(VirtualKeyCode::F12),
Keycode::Space => Some(VirtualKeyCode::Space),
Keycode::Escape => Some(VirtualKeyCode::Escape),
Keycode::Enter => Some(VirtualKeyCode::Return), // not on the Numpad
Keycode::Tab => Some(VirtualKeyCode::Tab),
Keycode::PageUp => Some(VirtualKeyCode::PageUp),
Keycode::PageDown => Some(VirtualKeyCode::PageDown),
Keycode::MoveHome => Some(VirtualKeyCode::Home),
Keycode::MoveEnd => Some(VirtualKeyCode::End),
Keycode::Insert => Some(VirtualKeyCode::Insert),
Keycode::Del => Some(VirtualKeyCode::Back), // Backspace (above Enter)
Keycode::ForwardDel => Some(VirtualKeyCode::Delete), // Delete (below Insert)
Keycode::Copy => Some(VirtualKeyCode::Copy),
Keycode::Paste => Some(VirtualKeyCode::Paste),
Keycode::Cut => Some(VirtualKeyCode::Cut),
Keycode::VolumeUp => Some(VirtualKeyCode::VolumeUp),
Keycode::VolumeDown => Some(VirtualKeyCode::VolumeDown),
Keycode::VolumeMute => Some(VirtualKeyCode::Mute), // ???
Keycode::Mute => Some(VirtualKeyCode::Mute), // ???
Keycode::MediaPlayPause => Some(VirtualKeyCode::PlayPause),
Keycode::MediaStop => Some(VirtualKeyCode::MediaStop), // ??? simple "Stop"?
Keycode::MediaNext => Some(VirtualKeyCode::NextTrack),
Keycode::MediaPrevious => Some(VirtualKeyCode::PrevTrack),
Keycode::Plus => Some(VirtualKeyCode::Plus),
Keycode::Minus => Some(VirtualKeyCode::Minus),
Keycode::Equals => Some(VirtualKeyCode::Equals),
Keycode::Semicolon => Some(VirtualKeyCode::Semicolon),
Keycode::Slash => Some(VirtualKeyCode::Slash),
Keycode::Backslash => Some(VirtualKeyCode::Backslash),
Keycode::Comma => Some(VirtualKeyCode::Comma),
Keycode::Period => Some(VirtualKeyCode::Period),
Keycode::Apostrophe => Some(VirtualKeyCode::Apostrophe),
Keycode::Grave => Some(VirtualKeyCode::Grave),
Keycode::At => Some(VirtualKeyCode::At),
// TODO: Maybe mapping this to Snapshot makes more sense? See: "PrtScr/SysRq"
Keycode::Sysrq => Some(VirtualKeyCode::Sysrq),
// These are usually the same (Pause/Break)
Keycode::Break => Some(VirtualKeyCode::Pause),
// These are exactly the same
Keycode::ScrollLock => Some(VirtualKeyCode::Scroll),
Keycode::Yen => Some(VirtualKeyCode::Yen),
Keycode::Kana => Some(VirtualKeyCode::Kana),
Keycode::CtrlLeft => Some(VirtualKeyCode::LControl),
Keycode::CtrlRight => Some(VirtualKeyCode::RControl),
Keycode::ShiftLeft => Some(VirtualKeyCode::LShift),
Keycode::ShiftRight => Some(VirtualKeyCode::RShift),
Keycode::AltLeft => Some(VirtualKeyCode::LAlt),
Keycode::AltRight => Some(VirtualKeyCode::RAlt),
// Different names for the same keys
Keycode::MetaLeft => Some(VirtualKeyCode::LWin),
Keycode::MetaRight => Some(VirtualKeyCode::RWin),
Keycode::LeftBracket => Some(VirtualKeyCode::LBracket),
Keycode::RightBracket => Some(VirtualKeyCode::RBracket),
Keycode::Power => Some(VirtualKeyCode::Power),
Keycode::Sleep => Some(VirtualKeyCode::Sleep), // what about SoftSleep?
Keycode::Wakeup => Some(VirtualKeyCode::Wake),
Keycode::NavigateNext => Some(VirtualKeyCode::NavigateForward),
Keycode::NavigatePrevious => Some(VirtualKeyCode::NavigateBackward),
Keycode::Calculator => Some(VirtualKeyCode::Calculator),
Keycode::Explorer => Some(VirtualKeyCode::MyComputer), // "close enough"
Keycode::Envelope => Some(VirtualKeyCode::Mail), // "close enough"
Keycode::Star => Some(VirtualKeyCode::Asterisk), // ???
Keycode::AllApps => Some(VirtualKeyCode::Apps), // ???
Keycode::AppSwitch => Some(VirtualKeyCode::Apps), // ???
Keycode::Refresh => Some(VirtualKeyCode::WebRefresh), // ???
_ => None,
}
}
fn poll(poll: Poll) -> Option<EventSource> {
match poll {
Poll::Event { ident, .. } => match ident {
@@ -64,25 +237,31 @@ fn poll(poll: Poll) -> Option<EventSource> {
pub struct EventLoop<T: 'static> {
window_target: event_loop::EventLoopWindowTarget<T>,
user_queue: Arc<Mutex<VecDeque<T>>>,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: mpsc::Receiver<T>,
first_event: Option<EventSource>,
start_cause: event::StartCause,
looper: ThreadLooper,
running: bool,
window_lock: Option<LockReadGuard<NativeWindow>>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
macro_rules! call_event_handler {
( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{
if $cf != ControlFlow::Exit {
$event_handler($event, $window_target, &mut $cf);
if let ControlFlow::ExitWithCode(code) = $cf {
$event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code));
} else {
$event_handler($event, $window_target, &mut ControlFlow::Exit);
$event_handler($event, $window_target, &mut $cf);
}
}};
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Self {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self {
let (user_events_sender, user_events_receiver) = mpsc::channel();
Self {
window_target: event_loop::EventLoopWindowTarget {
p: EventLoopWindowTarget {
@@ -90,11 +269,13 @@ impl<T: 'static> EventLoop<T> {
},
_marker: std::marker::PhantomData,
},
user_queue: Default::default(),
user_events_sender,
user_events_receiver,
first_event: None,
start_cause: event::StartCause::Init,
looper: ThreadLooper::for_thread().unwrap(),
running: false,
window_lock: None,
}
}
@@ -103,11 +284,11 @@ impl<T: 'static> EventLoop<T> {
F: 'static
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{
self.run_return(event_handler);
::std::process::exit(0);
let exit_code = self.run_return(event_handler);
::std::process::exit(exit_code);
}
pub fn run_return<F>(&mut self, mut event_handler: F)
pub fn run_return<F>(&mut self, mut event_handler: F) -> i32
where
F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{
@@ -127,26 +308,47 @@ impl<T: 'static> EventLoop<T> {
match self.first_event.take() {
Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() {
Event::WindowCreated => {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Resumed
);
// Acquire a lock on the window to prevent Android from destroying
// it until we've notified and waited for the user in Event::Suspended.
// WARNING: ndk-glue is inherently racy (https://github.com/rust-windowing/winit/issues/2293)
// and may have already received onNativeWindowDestroyed while this thread hasn't yet processed
// the event, and would see a `None` lock+window in that case.
if let Some(next_window_lock) = ndk_glue::native_window() {
assert!(
self.window_lock.replace(next_window_lock).is_none(),
"Received `Event::WindowCreated` while we were already holding a lock"
);
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Resumed
);
} else {
warn!("Received `Event::WindowCreated` while `ndk_glue::native_window()` provides no window");
}
}
Event::WindowResized => resized = true,
Event::WindowRedrawNeeded => redraw = true,
Event::WindowDestroyed => {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Suspended
);
// Release the lock, allowing Android to clean up this surface
// WARNING: See above - if ndk-glue is racy, this event may be called
// without having a `self.window_lock` in place.
if self.window_lock.take().is_some() {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Suspended
);
} else {
warn!("Received `Event::WindowDestroyed` while we were not holding a window lock");
}
}
Event::Pause => self.running = false,
Event::Resume => self.running = true,
Event::ConfigChanged => {
#[allow(deprecated)] // TODO: rust-windowing/winit#2196
let am = ndk_glue::native_activity().asset_manager();
let config = Configuration::from_asset_manager(&am);
let old_scale_factor = MonitorHandle.scale_factor();
@@ -195,7 +397,7 @@ impl<T: 'static> EventLoop<T> {
},
Some(EventSource::InputQueue) => {
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
while let Some(event) = input_queue.get_event() {
while let Some(event) = input_queue.get_event().expect("get_event") {
if let Some(event) = input_queue.pre_dispatch(event) {
let mut handled = true;
let window_id = window::WindowId(WindowId);
@@ -274,7 +476,9 @@ impl<T: 'static> EventLoop<T> {
input: event::KeyboardInput {
scancode: key.scan_code() as u32,
state,
virtual_keycode: None,
virtual_keycode: ndk_keycode_to_virtualkeycode(
key.key_code(),
),
modifiers: event::ModifiersState::default(),
},
is_synthetic: false,
@@ -294,8 +498,9 @@ impl<T: 'static> EventLoop<T> {
}
}
Some(EventSource::User) => {
let mut user_queue = self.user_queue.lock().unwrap();
while let Some(event) = user_queue.pop_front() {
// try_recv only errors when empty (expected) or disconnect. But because Self
// contains a Sender it will never disconnect, so no error handling need.
while let Ok(event) = self.user_events_receiver.try_recv() {
call_event_handler!(
event_handler,
self.window_target(),
@@ -339,7 +544,7 @@ impl<T: 'static> EventLoop<T> {
);
match control_flow {
ControlFlow::Exit => {
ControlFlow::ExitWithCode(code) => {
self.first_event = poll(
self.looper
.poll_once_timeout(Duration::from_millis(0))
@@ -349,7 +554,7 @@ impl<T: 'static> EventLoop<T> {
start: Instant::now(),
requested_resume: None,
};
break 'event_loop;
break 'event_loop code;
}
ControlFlow::Poll => {
self.first_event = poll(
@@ -396,20 +601,22 @@ impl<T: 'static> EventLoop<T> {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
queue: self.user_queue.clone(),
user_events_sender: self.user_events_sender.clone(),
looper: ForeignLooper::for_thread().expect("called from event loop thread"),
}
}
}
pub struct EventLoopProxy<T: 'static> {
queue: Arc<Mutex<VecDeque<T>>>,
user_events_sender: mpsc::Sender<T>,
looper: ForeignLooper,
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.queue.lock().unwrap().push_back(event);
self.user_events_sender
.send(event)
.map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?;
self.looper.wake();
Ok(())
}
@@ -418,7 +625,7 @@ impl<T> EventLoopProxy<T> {
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
queue: self.queue.clone(),
user_events_sender: self.user_events_sender.clone(),
looper: self.looper.clone(),
}
}
@@ -440,6 +647,10 @@ impl<T: 'static> EventLoopWindowTarget<T> {
v.push_back(MonitorHandle);
v
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -451,6 +662,18 @@ impl WindowId {
}
}
impl From<WindowId> for u64 {
fn from(_: WindowId) -> Self {
0
}
}
impl From<u64> for WindowId {
fn from(_: u64) -> Self {
Self
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DeviceId;
@@ -466,7 +689,7 @@ pub struct PlatformSpecificWindowBuilderAttributes;
pub struct Window;
impl Window {
pub fn new<T: 'static>(
pub(crate) fn new<T: 'static>(
_el: &EventLoopWindowTarget<T>,
_window_attrs: window::WindowAttributes,
_: PlatformSpecificWindowBuilderAttributes,
@@ -538,8 +761,16 @@ impl Window {
pub fn set_visible(&self, _visibility: bool) {}
pub fn is_visible(&self) -> Option<bool> {
None
}
pub fn set_resizable(&self, _resizeable: bool) {}
pub fn is_resizable(&self) -> bool {
false
}
pub fn set_minimized(&self, _minimized: bool) {}
pub fn set_maximized(&self, _maximized: bool) {}
@@ -558,12 +789,18 @@ impl Window {
pub fn set_decorations(&self, _decorations: bool) {}
pub fn is_decorated(&self) -> bool {
true
}
pub fn set_always_on_top(&self, _always_on_top: bool) {}
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
pub fn set_ime_position(&self, _position: Position) {}
pub fn set_ime_allowed(&self, _allowed: bool) {}
pub fn focus_window(&self) {}
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
@@ -576,7 +813,7 @@ impl Window {
))
}
pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> {
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
@@ -590,14 +827,22 @@ impl Window {
))
}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = AndroidNdkHandle::empty();
if let Some(native_window) = ndk_glue::native_window().as_ref() {
handle.a_native_window = unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
if let Some(native_window) = ndk_glue::native_window() {
native_window.raw_window_handle()
} else {
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
};
RawWindowHandle::AndroidNdk(handle)
}
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
}
pub fn config(&self) -> Configuration {
@@ -651,20 +896,23 @@ impl MonitorHandle {
.unwrap_or(1.0)
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
// FIXME no way to get real refresh rate for now.
None
}
pub fn video_modes(&self) -> impl Iterator<Item = monitor::VideoMode> {
let size = self.size().into();
let mut v = Vec::new();
// FIXME this is not the real refresh rate
// (it is guarunteed to support 32 bit color though)
v.push(monitor::VideoMode {
// (it is guaranteed to support 32 bit color though)
std::iter::once(monitor::VideoMode {
video_mode: VideoMode {
size,
bit_depth: 32,
refresh_rate: 60,
refresh_rate_millihertz: 60000,
monitor: self.clone(),
},
});
v.into_iter()
})
}
}
@@ -672,7 +920,7 @@ impl MonitorHandle {
pub struct VideoMode {
size: (u32, u32),
bit_depth: u16,
refresh_rate: u16,
refresh_rate_millihertz: u32,
monitor: MonitorHandle,
}
@@ -685,8 +933,8 @@ impl VideoMode {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> monitor::MonitorHandle {

View File

@@ -10,6 +10,7 @@ use std::{
};
use objc::runtime::{BOOL, YES};
use once_cell::sync::Lazy;
use crate::{
dpi::LogicalSize,
@@ -52,11 +53,7 @@ enum UserCallbackTransitionResult<'a> {
impl Event<'static, Never> {
fn is_redraw(&self) -> bool {
if let Event::RedrawRequested(_) = self {
true
} else {
false
}
matches!(self, Event::RedrawRequested(_))
}
}
@@ -119,7 +116,7 @@ impl Drop for AppState {
} => {
for &mut window in queued_windows {
unsafe {
let () = msg_send![window, release];
let _: () = msg_send![window, release];
}
}
}
@@ -200,10 +197,10 @@ impl AppState {
}
fn has_launched(&self) -> bool {
match self.state() {
&AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false,
_ => true,
}
!matches!(
self.state(),
AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }
)
}
fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
@@ -296,7 +293,7 @@ impl AppState {
};
(waiting_event_handler, event)
}
(ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"),
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
@@ -451,7 +448,7 @@ impl AppState {
});
self.waker.start()
}
(_, ControlFlow::Exit) => {
(_, ControlFlow::ExitWithCode(_)) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
@@ -528,7 +525,9 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
| &mut AppStateImpl::InUserCallback {
ref mut queued_gpu_redraws,
..
} => drop(queued_gpu_redraws.insert(window)),
} => {
let _ = queued_gpu_redraws.insert(window);
}
s @ &mut AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
@@ -536,6 +535,7 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
panic!("Attempt to create a `Window` after the app has terminated")
}
}
drop(this);
}
@@ -548,7 +548,7 @@ pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
pub unsafe fn did_finish_launching() {
let mut this = AppState::get_mut();
let windows = match this.state_mut() {
AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()),
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
s => bug!("unexpected state {:?}", s),
};
@@ -578,15 +578,15 @@ pub unsafe fn did_finish_launching() {
// ```
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 _: () = 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, setRootViewController:ptr::null::<()>()];
let _: () = msg_send![window, setRootViewController: controller];
let _: () = msg_send![window, makeKeyAndVisible];
}
let () = msg_send![window, release];
let _: () = msg_send![window, release];
}
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
@@ -603,9 +603,9 @@ pub unsafe fn did_finish_launching() {
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, makeKeyAndVisible];
}
let () = msg_send![window, release];
let _: () = msg_send![window, release];
}
}
@@ -670,7 +670,7 @@ pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
queued_gpu_redraws: _,
} => mem::replace(queued_events, Vec::new()),
} => mem::take(queued_events),
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
@@ -751,7 +751,7 @@ unsafe fn handle_user_events() {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
queued_gpu_redraws: _,
} => mem::replace(queued_events, Vec::new()),
} => mem::take(queued_events),
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
@@ -793,7 +793,7 @@ pub unsafe fn handle_main_events_cleared() {
return;
}
match this.state_mut() {
&mut AppStateImpl::ProcessingEvents { .. } => {}
AppStateImpl::ProcessingEvents { .. } => {}
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
};
drop(this);
@@ -811,9 +811,7 @@ pub unsafe fn handle_main_events_cleared() {
})
.collect();
if !redraw_events.is_empty() {
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
}
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
drop(this);
handle_nonuser_events(redraw_events);
@@ -877,7 +875,7 @@ fn handle_hidpi_proxy(
let size = CGSize::new(logical_size);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
unsafe {
let () = msg_send![view, setFrame: new_frame];
let _: () = msg_send![view, setFrame: new_frame];
}
}
@@ -992,20 +990,20 @@ macro_rules! os_capabilities {
}
os_capabilities! {
/// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
#[allow(unused)] // error message unused
safe_area_err_msg: "-[UIView safeAreaInsets]",
safe_area: 11-0,
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
home_indicator_hidden: 11-0,
/// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
defer_system_gestures: 11-0,
/// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
maximum_frames_per_second: 10-3,
/// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
#[allow(unused)] // error message unused
force_touch_err_msg: "-[UITouch force]",
force_touch: 9-0,
@@ -1018,29 +1016,27 @@ impl NSOperatingSystemVersion {
}
pub fn os_capabilities() -> OSCapabilities {
lazy_static! {
static ref OS_CAPABILITIES: OSCapabilities = {
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
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.
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
// 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]
};
version.into()
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,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
// 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]
};
}
version.into()
});
OS_CAPABILITIES.clone()
}

View File

@@ -7,6 +7,8 @@ use std::{
sync::mpsc::{self, Receiver, Sender},
};
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use crate::{
dpi::LogicalSize,
event::Event,
@@ -63,14 +65,21 @@ impl<T: 'static> EventLoopWindowTarget<T> {
Some(RootMonitorHandle { inner: monitor })
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
}
}
pub struct EventLoop<T: 'static> {
window_target: RootEventLoopWindowTarget<T>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
static mut SINGLETON_INIT: bool = false;
unsafe {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");

View File

@@ -88,7 +88,7 @@ pub enum UITouchPhase {
Cancelled,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
@@ -97,7 +97,7 @@ pub enum UIForceTouchCapability {
Available,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
@@ -144,10 +144,9 @@ impl From<Idiom> for UIUserInterfaceIdiom {
}
}
}
impl Into<Idiom> for UIUserInterfaceIdiom {
fn into(self) -> Idiom {
match self {
impl From<UIUserInterfaceIdiom> for Idiom {
fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom {
match ui_idiom {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
@@ -230,9 +229,9 @@ impl From<ScreenEdge> for UIRectEdge {
}
}
impl Into<ScreenEdge> for UIRectEdge {
fn into(self) -> ScreenEdge {
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
impl From<UIRectEdge> for ScreenEdge {
fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge {
let bits: u8 = ui_rect_edge.0.try_into().expect("invalid `UIRectEdge`");
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}

View File

@@ -56,13 +56,15 @@
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
#![cfg(target_os = "ios")]
#![allow(clippy::let_unit_value)]
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
if !msg_send![class!(NSThread), isMainThread] {
let is_main_thread: ::objc::runtime::BOOL = msg_send!(class!(NSThread), isMainThread);
if is_main_thread == ::objc::runtime::NO {
panic!($($t)*);
}
};
@@ -77,8 +79,10 @@ mod window;
use std::fmt;
pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
pub(crate) use self::{
event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
};
@@ -105,9 +109,7 @@ unsafe impl Sync for DeviceId {}
pub enum OsError {}
impl fmt::Display for OsError {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
_ => unreachable!(),
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "os error")
}
}

View File

@@ -17,7 +17,7 @@ use crate::{
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: NativeDisplayMode,
pub(crate) monitor: MonitorHandle,
}
@@ -30,7 +30,7 @@ unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.0, release];
let _: () = msg_send![self.0, release];
}
}
}
@@ -49,7 +49,7 @@ impl Clone for VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate: self.refresh_rate,
refresh_rate_millihertz: self.refresh_rate_millihertz,
screen_mode: self.screen_mode.clone(),
monitor: self.monitor.clone(),
}
@@ -59,30 +59,14 @@ impl Clone for VideoMode {
impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let os_capabilities = app_state::os_capabilities();
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
};
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);
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
refresh_rate_millihertz,
screen_mode,
monitor: MonitorHandle::retained_new(uiscreen),
}
@@ -96,8 +80,8 @@ impl VideoMode {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {
@@ -115,7 +99,7 @@ pub struct Inner {
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.uiscreen, release];
let _: () = msg_send![self.uiscreen, release];
}
}
}
@@ -168,7 +152,9 @@ impl Drop for MonitorHandle {
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this using the proper fmt API
#[derive(Debug)]
#[allow(dead_code)]
struct MonitorHandle {
name: Option<String>,
size: PhysicalSize<u32>,
@@ -191,7 +177,7 @@ 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 () = msg_send![uiscreen, retain];
let _: () = msg_send![uiscreen, retain];
}
MonitorHandle {
inner: Inner { uiscreen },
@@ -237,6 +223,10 @@ impl Inner {
}
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(self.uiscreen))
}
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let mut modes = BTreeSet::new();
unsafe {
@@ -255,6 +245,30 @@ impl Inner {
}
}
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
let refresh_rate_millihertz: NSInteger = unsafe {
let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond]
} else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
//
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
// supported, they are all guaranteed to have 60hz refresh rates. This does not
// correctly handle external displays. ProMotion displays support 120fps, but they were
// introduced at the same time as the `maximumFramesPerSecond` API.
//
// FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
60
}
};
refresh_rate_millihertz as u32 * 1000
}
// MonitorHandleExtIOS
impl Inner {
pub fn ui_screen(&self) -> id {

View File

@@ -112,14 +112,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
))),
);
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), drawRect: rect];
let _: () = msg_send![super(object, superclass), drawRect: rect];
}
}
extern "C" fn layout_subviews(object: &Object, _: Sel) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), layoutSubviews];
let _: () = msg_send![super(object, superclass), layoutSubviews];
let window: id = msg_send![object, window];
assert!(!window.is_null());
@@ -133,14 +133,14 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
}
.to_physical(scale_factor.into());
.to_physical(scale_factor as f64);
// If the app is started in landscape, the view frame and window bounds can be mismatched.
// The view frame will be in portrait and the window bounds in landscape. So apply the
// window bounds to the view frame to make it consistent.
let view_frame: CGRect = msg_send![object, frame];
if view_frame != window_bounds {
let () = msg_send![object, setFrame: window_bounds];
let _: () = msg_send![object, setFrame: window_bounds];
}
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
@@ -157,7 +157,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![
let _: () = msg_send![
super(object, superclass),
setContentScaleFactor: untrusted_scale_factor
];
@@ -180,7 +180,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
&& scale_factor > 0.0,
"invalid scale_factor set on UIView",
);
let scale_factor: f64 = scale_factor.into();
let scale_factor = scale_factor as f64;
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
@@ -340,7 +340,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
prefers_status_bar_hidden: BOOL,
setPrefersStatusBarHidden: |object| {
unsafe {
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
let _: () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
},
prefersStatusBarHidden,
@@ -353,7 +353,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
OSCapabilities::home_indicator_hidden_err_msg;
|object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
let _: () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
}
},
prefersHomeIndicatorAutoHidden,
@@ -363,7 +363,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
supported_orientations: UIInterfaceOrientationMask,
setSupportedInterfaceOrientations: |object| {
unsafe {
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
let _: () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
},
supportedInterfaceOrientations,
@@ -376,7 +376,7 @@ unsafe fn get_view_controller_class() -> &'static Class {
OSCapabilities::defer_system_gestures_err_msg;
|object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
let _: () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
},
preferredScreenEdgesDeferringSystemGestures,
@@ -398,7 +398,7 @@ unsafe fn get_window_class() -> &'static Class {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
}));
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
let _: () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
@@ -408,7 +408,7 @@ unsafe fn get_window_class() -> &'static Class {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
}));
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
let _: () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
@@ -429,7 +429,7 @@ unsafe fn get_window_class() -> &'static Class {
}
// requires main thread
pub unsafe fn create_view(
pub(crate) unsafe fn create_view(
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
@@ -440,16 +440,16 @@ pub unsafe fn create_view(
assert!(!view.is_null(), "Failed to create `UIView` instance");
let view: id = msg_send![view, initWithFrame: frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
let () = msg_send![view, setMultipleTouchEnabled: YES];
let _: () = msg_send![view, setMultipleTouchEnabled: YES];
if let Some(scale_factor) = platform_attributes.scale_factor {
let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
let _: () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
}
view
}
// requires main thread
pub unsafe fn create_view_controller(
pub(crate) unsafe fn create_view_controller(
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: id,
@@ -484,28 +484,28 @@ pub unsafe fn create_view_controller(
let edges: UIRectEdge = platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into();
let () = msg_send![
let _: () = msg_send![
view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
let () = msg_send![
let _: () = msg_send![
view_controller,
setSupportedInterfaceOrientations: supported_orientations
];
let () = msg_send![
let _: () = msg_send![
view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
let () = msg_send![
let _: () = msg_send![
view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
let () = msg_send![view_controller, setView: view];
let _: () = msg_send![view_controller, setView: view];
view_controller
}
// requires main thread
pub unsafe fn create_window(
pub(crate) unsafe fn create_window(
window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
@@ -520,11 +520,11 @@ pub unsafe fn create_window(
!window.is_null(),
"Failed to initialize `UIWindow` instance"
);
let () = msg_send![window, setRootViewController: view_controller];
let _: () = msg_send![window, setRootViewController: view_controller];
match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let uiscreen = video_mode.monitor().ui_screen() as id;
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => {

View File

@@ -1,10 +1,10 @@
use raw_window_handle::{RawWindowHandle, UiKitHandle};
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use objc::runtime::{Class, Object, BOOL, NO, YES};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use crate::{
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
@@ -23,7 +23,8 @@ use crate::{
monitor, view, EventLoopWindowTarget, MonitorHandle,
},
window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
},
};
@@ -37,9 +38,9 @@ pub struct Inner {
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];
let _: () = msg_send![self.view, release];
let _: () = msg_send![self.view_controller, release];
let _: () = msg_send![self.window, release];
}
}
}
@@ -52,14 +53,19 @@ impl Inner {
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe {
let () = msg_send![self.window, setHidden: NO];
let _: () = msg_send![self.window, setHidden: NO];
},
false => unsafe {
let () = msg_send![self.window, setHidden: YES];
let _: () = msg_send![self.window, setHidden: YES];
},
}
}
pub fn is_visible(&self) -> Option<bool> {
warn!("`Window::is_visible` is ignored on iOS");
None
}
pub fn request_redraw(&self) {
unsafe {
if self.gl_or_metal_backed {
@@ -73,7 +79,7 @@ impl Inner {
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window);
} else {
let () = msg_send![self.view, setNeedsDisplay];
let _: () = msg_send![self.view, setNeedsDisplay];
}
}
}
@@ -114,8 +120,8 @@ impl Inner {
},
size: screen_frame.size,
};
let bounds = self.from_screen_space(new_screen_frame);
let () = msg_send![self.window, setBounds: bounds];
let bounds = self.rect_from_screen_space(new_screen_frame);
let _: () = msg_send![self.window, setBounds: bounds];
}
}
@@ -159,6 +165,11 @@ impl Inner {
warn!("`Window::set_resizable` is ignored on iOS")
}
pub fn is_resizable(&self) -> bool {
warn!("`Window::is_resizable` is ignored on iOS");
false
}
pub fn scale_factor(&self) -> f64 {
unsafe {
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
@@ -174,7 +185,7 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -186,6 +197,10 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_minimized(&self, _minimized: bool) {
warn!("`Window::set_minimized` is ignored on iOS")
}
@@ -204,7 +219,8 @@ impl Inner {
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];
let _: () =
msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
uiscreen
}
Some(Fullscreen::Borderless(monitor)) => monitor
@@ -219,16 +235,16 @@ impl Inner {
// 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];
let _: () = msg_send![self.window, setScreen: uiscreen];
}
let bounds: CGRect = msg_send![uiscreen, bounds];
let () = msg_send![self.window, setFrame: 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![
let _: () = msg_send![
uiscreen,
setOverscanCompensation: UIScreenOverscanCompensation::None
];
@@ -259,6 +275,11 @@ impl Inner {
warn!("`Window::set_decorations` is ignored on iOS")
}
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")
}
@@ -271,6 +292,10 @@ impl Inner {
warn!("`Window::set_ime_position` is ignored on iOS")
}
pub fn set_ime_allowed(&self, _allowed: bool) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn focus_window(&self) {
warn!("`Window::set_focus` is ignored on iOS")
}
@@ -307,11 +332,15 @@ impl Inner {
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = UiKitHandle::empty();
handle.ui_window = self.window as _;
handle.ui_view = self.view as _;
handle.ui_view_controller = self.view_controller as _;
RawWindowHandle::UiKit(handle)
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 _;
RawWindowHandle::UiKit(window_handle)
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
}
}
@@ -351,15 +380,15 @@ impl DerefMut for Window {
}
impl Window {
pub fn new<T>(
pub(crate) fn new<T>(
_event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
if let Some(_) = window_attributes.min_inner_size {
if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
}
if let Some(_) = window_attributes.max_inner_size {
if window_attributes.max_inner_size.is_some() {
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
}
if window_attributes.always_on_top {
@@ -395,7 +424,7 @@ impl Window {
None => screen_bounds,
};
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
let view = view::create_view(&window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = {
let view_class: id = msg_send![view, class];
@@ -428,7 +457,7 @@ impl Window {
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor: f64 = scale_factor.into();
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];
@@ -478,7 +507,7 @@ impl Inner {
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
);
let scale_factor = scale_factor as CGFloat;
let () = msg_send![self.view, setContentScaleFactor: scale_factor];
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor];
}
}
@@ -499,7 +528,7 @@ impl Inner {
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
unsafe {
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
let () = msg_send![
let _: () = msg_send![
self.view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
@@ -509,7 +538,7 @@ impl Inner {
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
unsafe {
let () = msg_send![
let _: () = msg_send![
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
@@ -519,7 +548,7 @@ impl Inner {
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
unsafe {
let status_bar_hidden = if hidden { YES } else { NO };
let () = msg_send![
let _: () = msg_send![
self.view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
@@ -530,11 +559,11 @@ impl Inner {
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
self.to_screen_space(msg_send![self.window, bounds])
self.rect_to_screen_space(msg_send![self.window, bounds])
}
// requires main thread
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
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];
@@ -545,7 +574,7 @@ impl Inner {
}
// requires main thread
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
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];
@@ -570,9 +599,9 @@ impl Inner {
height: bounds.size.height - safe_area.top - safe_area.bottom,
},
};
self.to_screen_space(safe_bounds)
self.rect_to_screen_space(safe_bounds)
} else {
let screen_frame = self.to_screen_space(bounds);
let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame: CGRect = {
let app: id = msg_send![class!(UIApplication), sharedApplication];
assert!(
@@ -616,6 +645,20 @@ impl WindowId {
}
}
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.window as u64
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self {
window: raw_id as _,
}
}
}
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}

View File

@@ -11,26 +11,35 @@ compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(feature = "wayland")]
use std::error::Error;
use std::{collections::VecDeque, env, fmt};
#[cfg(feature = "x11")]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
#[cfg(feature = "x11")]
use once_cell::sync::Lazy;
#[cfg(feature = "x11")]
use parking_lot::Mutex;
use raw_window_handle::RawWindowHandle;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
#[cfg(feature = "x11")]
pub use self::x11::XNotSupported;
#[cfg(feature = "x11")]
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;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
event_loop::{
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
window::{CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
@@ -49,8 +58,35 @@ pub mod x11;
/// If this variable is set with any other value, winit will panic.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend {
#[cfg(feature = "x11")]
X,
#[cfg(feature = "wayland")]
Wayland,
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) forced_backend: Option<Backend>,
pub(crate) any_thread: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApplicationName {
pub general: String,
pub instance: String,
}
impl ApplicationName {
pub fn new(general: String, instance: String) -> Self {
Self { general, instance }
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub name: Option<ApplicationName>,
#[cfg(feature = "x11")]
pub visual_infos: Option<XVisualInfo>,
#[cfg(feature = "x11")]
@@ -60,20 +96,19 @@ pub struct PlatformSpecificWindowBuilderAttributes {
#[cfg(feature = "x11")]
pub base_size: Option<Size>,
#[cfg(feature = "x11")]
pub class: Option<(String, String)>,
#[cfg(feature = "x11")]
pub override_redirect: bool,
#[cfg(feature = "x11")]
pub x11_window_types: Vec<XWindowType>,
#[cfg(feature = "x11")]
pub gtk_theme_variant: Option<String>,
#[cfg(feature = "wayland")]
pub app_id: Option<String>,
pub csd_theme: Option<Theme>,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
name: None,
#[cfg(feature = "x11")]
visual_infos: None,
#[cfg(feature = "x11")]
@@ -83,24 +118,20 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
#[cfg(feature = "x11")]
base_size: None,
#[cfg(feature = "x11")]
class: None,
#[cfg(feature = "x11")]
override_redirect: false,
#[cfg(feature = "x11")]
x11_window_types: vec![XWindowType::Normal],
#[cfg(feature = "x11")]
gtk_theme_variant: None,
#[cfg(feature = "wayland")]
app_id: None,
csd_theme: None,
}
}
}
#[cfg(feature = "x11")]
lazy_static! {
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new));
}
pub 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 {
@@ -118,9 +149,9 @@ impl fmt::Display for OsError {
#[cfg(feature = "x11")]
OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(feature = "x11")]
OsError::XMisc(ref e) => _f.pad(e),
OsError::XMisc(e) => _f.pad(e),
#[cfg(feature = "wayland")]
OsError::WaylandMisc(ref e) => _f.pad(e),
OsError::WaylandMisc(e) => _f.pad(e),
}
}
}
@@ -133,19 +164,23 @@ pub enum Window {
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WindowId {
#[cfg(feature = "x11")]
X(x11::WindowId),
#[cfg(feature = "wayland")]
Wayland(wayland::WindowId),
pub struct WindowId(u64);
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id)
}
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
#[cfg(feature = "wayland")]
return WindowId::Wayland(wayland::WindowId::dummy());
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
return WindowId::X(x11::WindowId::dummy());
Self(0)
}
}
@@ -223,6 +258,11 @@ impl MonitorHandle {
x11_or_wayland!(match self; MonitorHandle(m) => m.position())
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz())
}
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64)
@@ -254,8 +294,8 @@ impl VideoMode {
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate())
pub fn refresh_rate_millihertz(&self) -> u32 {
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz())
}
#[inline]
@@ -266,7 +306,7 @@ impl VideoMode {
impl Window {
#[inline]
pub fn new<T>(
pub(crate) fn new<T>(
window_target: &EventLoopWindowTarget<T>,
attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
@@ -285,7 +325,12 @@ impl Window {
#[inline]
pub fn id(&self) -> WindowId {
x11_or_wayland!(match self; Window(w) => w.id(); as WindowId)
match self {
#[cfg(feature = "wayland")]
Self::Wayland(window) => window.id(),
#[cfg(feature = "x11")]
Self::X(window) => window.id(),
}
}
#[inline]
@@ -298,6 +343,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
x11_or_wayland!(match self; Window(w) => w.is_visible())
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.outer_position())
@@ -343,14 +393,19 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
}
#[inline]
pub fn is_resizable(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_resizable())
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab))
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
}
#[inline]
@@ -363,6 +418,11 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.drag_window())
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
}
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
@@ -403,13 +463,18 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations))
}
#[inline]
pub fn is_decorated(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_decorated())
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
match self {
#[cfg(feature = "x11")]
Window::X(ref w) => w.set_always_on_top(_always_on_top),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
@@ -419,7 +484,7 @@ impl Window {
#[cfg(feature = "x11")]
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
@@ -428,13 +493,18 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
}
#[inline]
pub fn focus_window(&self) {
match self {
#[cfg(feature = "x11")]
Window::X(ref w) => w.focus_window(),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
@@ -504,16 +574,22 @@ impl Window {
}
}
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
match self {
#[cfg(feature = "x11")]
Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
#[cfg(feature = "wayland")]
Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
}
x11_or_wayland!(match self; Window(window) => window.raw_window_handle())
}
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
}
}
/// Hooks for X11 errors.
#[cfg(feature = "x11")]
pub(crate) static mut XLIB_ERROR_HOOKS: Lazy<Mutex<Vec<XlibErrorHook>>> =
Lazy::new(|| Mutex::new(Vec::new()));
#[cfg(feature = "x11")]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
@@ -521,6 +597,12 @@ unsafe extern "C" fn x_error_callback(
) -> c_int {
let xconn_lock = X11_BACKEND.lock();
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
@@ -539,7 +621,10 @@ unsafe extern "C" fn x_error_callback(
minor_code: (*event).minor_code,
};
error!("X11 error: {:#?}", error);
// Don't log error.
if !error_handled {
error!("X11 error: {:#?}", error);
}
*xconn.latest_error.lock() = Some(error);
}
@@ -549,7 +634,7 @@ unsafe extern "C" fn x_error_callback(
pub enum EventLoop<T: 'static> {
#[cfg(feature = "wayland")]
Wayland(wayland::EventLoop<T>),
Wayland(Box<wayland::EventLoop<T>>),
#[cfg(feature = "x11")]
X(x11::EventLoop<T>),
}
@@ -568,13 +653,28 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> {
assert_is_main_thread("new_any_thread");
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
if !attributes.any_thread && !is_main_thread() {
panic!(
"Initializing the event loop outside of the main thread is a significant \
cross-platform compatibility hazard. If you absolutely need to create an \
EventLoop on a different thread, you can use the \
`EventLoopBuilderExtUnix::any_thread` function."
);
}
EventLoop::new_any_thread()
}
#[cfg(feature = "x11")]
if attributes.forced_backend == Some(Backend::X) {
// TODO: Propagate
return EventLoop::new_x11_any_thread().unwrap();
}
#[cfg(feature = "wayland")]
if attributes.forced_backend == Some(Backend::Wayland) {
// TODO: Propagate
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
}
pub fn new_any_thread() -> EventLoop<T> {
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
match env_var.as_str() {
"x11" => {
@@ -623,26 +723,12 @@ impl<T: 'static> EventLoop<T> {
}
#[cfg(feature = "wayland")]
pub fn new_wayland() -> Result<EventLoop<T>, Box<dyn Error>> {
assert_is_main_thread("new_wayland_any_thread");
EventLoop::new_wayland_any_thread()
}
#[cfg(feature = "wayland")]
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
wayland::EventLoop::new().map(EventLoop::Wayland)
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
#[cfg(feature = "x11")]
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
assert_is_main_thread("new_x11_any_thread");
EventLoop::new_x11_any_thread()
}
#[cfg(feature = "x11")]
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(err.clone()),
@@ -655,7 +741,7 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run_return<F>(&mut self, callback: F)
pub fn run_return<F>(&mut self, callback: F) -> i32
where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
@@ -670,7 +756,7 @@ impl<T: 'static> EventLoop<T> {
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
x11_or_wayland!(match self; EventLoop(evl) => evl.window_target())
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
}
}
@@ -731,6 +817,20 @@ impl<T> EventLoopWindowTarget<T> {
}
}
}
#[inline]
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
match *self {
#[cfg(feature = "wayland")]
EventLoopWindowTarget::Wayland(_) => (),
#[cfg(feature = "x11")]
EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter),
}
}
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle())
}
}
fn sticky_exit_callback<T, F>(
@@ -741,26 +841,12 @@ fn sticky_exit_callback<T, F>(
) where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::Exit sticky by providing a dummy
// control flow reference if it is already Exit.
let mut dummy = ControlFlow::Exit;
let cf = if *control_flow == ControlFlow::Exit {
&mut dummy
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
if let ControlFlow::ExitWithCode(code) = *control_flow {
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
} else {
control_flow
};
// user callback
callback(evt, target, cf)
}
fn assert_is_main_thread(suggested_method: &str) {
if !is_main_thread() {
panic!(
"Initializing the event loop outside of the main thread is a significant \
cross-platform compatibility hazard. If you really, absolutely need to create an \
EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.",
suggested_method
);
callback(evt, target, control_flow)
}
}

View File

@@ -24,23 +24,23 @@ use sctk::shm::ShmHandler;
/// Set of extra features that are supported by the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
cursor_grab: bool,
pointer_constraints: bool,
xdg_activation: bool,
}
impl WindowingFeatures {
/// Create `WindowingFeatures` based on the presented interfaces.
pub fn new(env: &Environment<WinitEnv>) -> Self {
let cursor_grab = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
cursor_grab,
pointer_constraints,
xdg_activation,
}
}
pub fn cursor_grab(&self) -> bool {
self.cursor_grab
pub fn pointer_constraints(&self) -> bool {
self.pointer_constraints
}
pub fn xdg_activation(&self) -> bool {

View File

@@ -6,6 +6,8 @@ use std::process;
use std::rc::Rc;
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;
@@ -32,10 +34,9 @@ mod sink;
mod state;
pub use proxy::EventLoopProxy;
pub use sink::EventSink;
pub use state::WinitState;
use sink::EventSink;
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
pub struct EventLoopWindowTarget<T> {
@@ -65,13 +66,21 @@ pub struct EventLoopWindowTarget<T> {
/// Theme manager to manage cursors.
///
/// It's being shared amoung all windows to avoid loading
/// 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)
}
}
pub struct EventLoop<T: 'static> {
/// Event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
@@ -206,19 +215,15 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
{
self.run_return(callback);
process::exit(0)
let exit_code = self.run_return(callback);
process::exit(exit_code);
}
pub fn run_return<F>(&mut self, mut callback: F)
pub fn run_return<F>(&mut self, mut callback: F) -> i32
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
// Send pending events to the server.
let _ = self.display.flush();
let mut control_flow = ControlFlow::default();
let mut control_flow = ControlFlow::Poll;
let pending_user_events = self.pending_user_events.clone();
callback(
@@ -227,6 +232,10 @@ impl<T: 'static> EventLoop<T> {
&mut control_flow,
);
// NB: 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_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
let mut event_sink_back_buffer = Vec::new();
@@ -235,7 +244,108 @@ impl<T: 'static> EventLoop<T> {
// 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.
loop {
// 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();
// During the run of the user callback, some other code monitoring and reading the
// Wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the Wayland socket, to avoid delaying the
// dispatch of these events until we're woken up again.
let instant_wakeup = {
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let queue = wayland_source.queue();
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
}
#[cfg(feature = "x11")]
_ => unreachable!(),
};
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
Ok(dispatched) => dispatched > 0,
Err(error) => break error.raw_os_error().unwrap_or(1),
}
};
match control_flow {
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::from_millis(0);
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
}
callback(
Event::NewEvents(StartCause::Poll),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::from_millis(0))
} else {
None
};
if let Err(error) = self.loop_dispatch(timeout) {
break error.raw_os_error().unwrap_or(1);
}
callback(
Event::NewEvents(StartCause::WaitCancelled {
start: Instant::now(),
requested_resume: None,
}),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::WaitUntil(deadline) => {
let start = Instant::now();
// Compute the amount of time we'll block for.
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::from_millis(0)
};
if let Err(error) = self.loop_dispatch(Some(duration)) {
break error.raw_os_error().unwrap_or(1);
}
let now = Instant::now();
if now < deadline {
callback(
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}),
&self.window_target,
&mut control_flow,
)
} else {
callback(
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}),
&self.window_target,
&mut control_flow,
)
}
}
}
// 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(..) {
@@ -273,9 +383,7 @@ impl<T: 'static> EventLoop<T> {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
),
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
@@ -325,9 +433,7 @@ impl<T: 'static> EventLoop<T> {
if let Some(physical_size) = physical_size {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
),
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
@@ -340,9 +446,7 @@ impl<T: 'static> EventLoop<T> {
if window_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
),
window_id: crate::window::WindowId(*window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
@@ -392,9 +496,7 @@ impl<T: 'static> EventLoop<T> {
// Handle redraw request.
if window_update.redraw_requested {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(
crate::platform_impl::WindowId::Wayland(*window_id),
)),
Event::RedrawRequested(crate::window::WindowId(*window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
@@ -409,110 +511,10 @@ impl<T: 'static> EventLoop<T> {
&mut control_flow,
&mut callback,
);
// Send pending events to the server.
let _ = self.display.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
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the Wayland socket, to avoid delaying the
// dispatch of these events until we're woken up again.
let instant_wakeup = {
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let queue = wayland_source.queue();
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
}
#[cfg(feature = "x11")]
_ => unreachable!(),
};
if let Ok(dispatched) = queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
dispatched > 0
} else {
break;
}
};
match control_flow {
ControlFlow::Exit => break,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::from_millis(0);
if self.loop_dispatch(Some(timeout)).is_err() {
break;
}
callback(
Event::NewEvents(StartCause::Poll),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::from_millis(0))
} else {
None
};
if self.loop_dispatch(timeout).is_err() {
break;
}
callback(
Event::NewEvents(StartCause::WaitCancelled {
start: Instant::now(),
requested_resume: None,
}),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::WaitUntil(deadline) => {
let start = Instant::now();
// Compute the amount of time we'll block for.
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::from_millis(0)
};
if self.loop_dispatch(Some(duration)).is_err() {
break;
}
let now = Instant::now();
if now < deadline {
callback(
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}),
&self.window_target,
&mut control_flow,
)
} else {
callback(
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}),
&self.window_target,
&mut control_flow,
)
}
}
}
}
};
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
exit_code
}
#[inline]
@@ -536,12 +538,14 @@ impl<T: 'static> EventLoop<T> {
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
let mut state = match &mut self.window_target.p {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
self.event_loop.dispatch(timeout, &mut state)
self.event_loop
.dispatch(timeout, state)
.map_err(|error| error.into())
}
}

View File

@@ -1,7 +1,7 @@
//! An event loop's sink to deliver events from the Wayland event callbacks.
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
use crate::platform_impl::platform::{DeviceId as PlatformDeviceId, WindowId as PlatformWindowId};
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
use crate::window::WindowId as RootWindowId;
use super::{DeviceId, WindowId};
@@ -30,7 +30,7 @@ impl EventSink {
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(PlatformWindowId::Wayland(window_id)),
window_id: RootWindowId(window_id),
});
}
}

View File

@@ -8,6 +8,7 @@
use sctk::reexports::client::protocol::wl_surface::WlSurface;
pub use crate::platform_impl::platform::WindowId;
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
pub use window::Window;
@@ -27,16 +28,7 @@ impl DeviceId {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize);
impl WindowId {
pub const unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[inline]
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.as_ref().c_ptr() as usize)
WindowId(surface.as_ref().c_ptr() as u64)
}

View File

@@ -167,6 +167,16 @@ impl MonitorHandle {
.into()
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
sctk::output::with_output_info(&self.proxy, |info| {
info.modes
.iter()
.find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32))
})
.flatten()
}
#[inline]
pub fn scale_factor(&self) -> i32 {
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
@@ -175,14 +185,14 @@ impl MonitorHandle {
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
.unwrap_or_else(Vec::new);
.unwrap_or_default();
let monitor = self.clone();
modes.into_iter().map(move |mode| RootVideoMode {
video_mode: PlatformVideoMode::Wayland(VideoMode {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16,
refresh_rate_millihertz: mode.refresh_rate as u32,
bit_depth: 32,
monitor: monitor.clone(),
}),
@@ -194,7 +204,7 @@ impl MonitorHandle {
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) monitor: MonitorHandle,
}
@@ -210,8 +220,8 @@ impl VideoMode {
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> RootMonitorHandle {

View File

@@ -7,7 +7,7 @@ use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::reexports::calloop::{LoopHandle, RegistrationToken};
use sctk::reexports::calloop::LoopHandle;
use sctk::seat::keyboard;
@@ -20,12 +20,6 @@ mod keymap;
pub(crate) struct Keyboard {
pub keyboard: WlKeyboard,
/// The source for repeat keys.
pub repeat_token: Option<RegistrationToken>,
/// LoopHandle to drop `RepeatSource`, when dropping the keyboard.
pub loop_handle: LoopHandle<'static, WinitState>,
}
impl Keyboard {
@@ -35,7 +29,7 @@ impl Keyboard {
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Option<Self> {
let mut inner = KeyboardInner::new(modifiers_state);
let keyboard_data = keyboard::map_keyboard_repeat(
let keyboard = keyboard::map_keyboard_repeat(
loop_handle.clone(),
seat,
None,
@@ -44,15 +38,10 @@ impl Keyboard {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_keyboard(event, &mut inner, winit_state);
},
);
)
.ok()?;
let (keyboard, repeat_token) = keyboard_data.ok()?;
Some(Self {
keyboard,
loop_handle,
repeat_token: Some(repeat_token),
})
Some(Self { keyboard })
}
}
@@ -61,10 +50,6 @@ impl Drop for Keyboard {
if self.keyboard.as_ref().version() >= 3 {
self.keyboard.release();
}
if let Some(repeat_token) = self.repeat_token.take() {
self.loop_handle.remove(repeat_token);
}
}
}

View File

@@ -5,8 +5,9 @@ use std::rc::Rc;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1};
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use crate::event::{ModifiersState, TouchPhase};
@@ -25,10 +26,14 @@ pub(super) struct PointerData {
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
/// A latest event serial.
/// Latest observed serial in pointer events.
pub latest_serial: Rc<Cell<u32>>,
/// Latest observed serial in pointer enter events.
pub latest_enter_serial: Rc<Cell<u32>>,
/// The currently accumulated axis data on a pointer.
pub axis_data: AxisData,
}
@@ -36,13 +41,16 @@ pub(super) struct PointerData {
impl PointerData {
pub fn new(
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
Self {
surface: None,
latest_serial: Rc::new(Cell::new(0)),
latest_enter_serial: Rc::new(Cell::new(0)),
confined_pointer,
locked_pointer,
modifiers_state,
pointer_constraints,
axis_data: AxisData::new(),

View File

@@ -42,6 +42,7 @@ pub(super) fn handle_pointer(
..
} => {
pointer_data.latest_serial.replace(serial);
pointer_data.latest_enter_serial.replace(serial);
let window_id = wayland::make_wid(&surface);
if !winit_state.window_map.contains_key(&window_id) {
@@ -59,8 +60,10 @@ pub(super) fn handle_pointer(
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
seat,
};
window_handle.pointer_entered(winit_pointer);
@@ -102,8 +105,10 @@ pub(super) fn handle_pointer(
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
seat,
};
window_handle.pointer_left(winit_pointer);
@@ -193,9 +198,9 @@ pub(super) fn handle_pointer(
// Old seat compatibility.
match axis {
// Wayland vertical sign convention is the inverse of winit.
// Wayland sign convention is the inverse of winit.
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
_ => unreachable!(),
}
@@ -216,9 +221,9 @@ pub(super) fn handle_pointer(
} else {
let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0));
match axis {
// Wayland vertical sign convention is the inverse of winit.
// Wayland sign convention is the inverse of winit.
wl_pointer::Axis::VerticalScroll => y -= value as f32,
wl_pointer::Axis::HorizontalScroll => x += value as f32,
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
_ => unreachable!(),
}
@@ -237,9 +242,9 @@ pub(super) fn handle_pointer(
.unwrap_or((0., 0.));
match axis {
// Wayland vertical sign convention is the inverse of winit.
// Wayland sign convention is the inverse of winit.
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
wl_pointer::Axis::HorizontalScroll => x += discrete as f32,
wl_pointer::Axis::HorizontalScroll => x -= discrete as f32,
_ => unreachable!(),
}
@@ -297,9 +302,17 @@ pub(super) fn handle_pointer(
#[inline]
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
if let RelativePointerEvent::RelativeMotion { dx, dy, .. } = event {
winit_state
.event_sink
.push_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
if let RelativePointerEvent::RelativeMotion {
dx_unaccel,
dy_unaccel,
..
} = event
{
winit_state.event_sink.push_device_event(
DeviceEvent::MouseMotion {
delta: (dx_unaccel, dy_unaccel),
},
DeviceId,
)
}
}

View File

@@ -11,12 +11,14 @@ use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_rela
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::{FallbackFrame, Window};
use sctk::window::Window;
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::window::WinitFrame;
use crate::window::CursorIcon;
mod data;
@@ -34,9 +36,17 @@ pub struct WinitPointer {
/// Cursor to handle confine requests.
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
/// Cursor to handle locked requests.
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
/// Latest observed serial in pointer events.
/// used by Window::start_interactive_move()
latest_serial: Rc<Cell<u32>>,
/// Latest observed serial in pointer enter events.
/// used by Window::set_cursor()
latest_enter_serial: Rc<Cell<u32>>,
/// Seat.
seat: WlSeat,
}
@@ -58,7 +68,9 @@ impl WinitPointer {
Some(cursor_icon) => cursor_icon,
None => {
// Hide the cursor.
(*self.pointer).set_cursor(self.latest_serial.get(), None, 0, 0);
// WlPointer::set_cursor() expects the serial of the last *enter*
// event (compare to to start_interactive_move()).
(*self.pointer).set_cursor(self.latest_enter_serial.get(), None, 0, 0);
return;
}
};
@@ -106,7 +118,7 @@ impl WinitPointer {
CursorIcon::ZoomOut => &["zoom-out"],
};
let serial = Some(self.latest_serial.get());
let serial = Some(self.latest_enter_serial.get());
for cursor in cursors {
if self.pointer.set_cursor(cursor, serial).is_ok() {
return;
@@ -150,7 +162,55 @@ impl WinitPointer {
}
}
pub fn drag_window(&self, window: &Window<FallbackFrame>) {
pub fn lock(&self, surface: &WlSurface) {
let pointer_constraints = match &self.pointer_constraints {
Some(pointer_constraints) => pointer_constraints,
None => return,
};
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
pointer_constraints,
surface,
&*self.pointer,
));
}
pub fn unlock(&self) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let mut locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.take() {
locked_pointer.destroy();
}
}
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.as_ref() {
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
}
}
pub fn drag_window(&self, window: &Window<WinitFrame>) {
// WlPointer::setart_interactive_move() expects the last serial of *any*
// pointer event (compare to set_cursor()).
window.start_interactive_move(&self.seat, self.latest_serial.get());
}
}
@@ -165,6 +225,9 @@ pub(super) struct Pointers {
/// Confined pointer.
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
/// Locked pointer.
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
}
impl Pointers {
@@ -176,11 +239,15 @@ impl Pointers {
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
let confined_pointer = Rc::new(RefCell::new(None));
let locked_pointer = Rc::new(RefCell::new(None));
let pointer_data = Rc::new(RefCell::new(PointerData::new(
confined_pointer.clone(),
locked_pointer.clone(),
pointer_constraints.clone(),
modifiers_state,
)));
let pointer_seat = seat.detach();
let pointer = theme_manager.theme_pointer_with_impl(
seat,
@@ -207,6 +274,7 @@ impl Pointers {
pointer,
relative_pointer,
confined_pointer,
locked_pointer,
}
}
}
@@ -223,6 +291,11 @@ impl Drop for Pointers {
confined_pointer.destroy();
}
// Drop lock ponter.
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
locked_pointer.destroy();
}
// Drop the pointer itself in case it's possible.
if self.pointer.as_ref().version() >= 3 {
self.pointer.release();
@@ -234,7 +307,7 @@ pub(super) fn init_relative_pointer(
relative_pointer_manager: &ZwpRelativePointerManagerV1,
pointer: &WlPointer,
) -> ZwpRelativePointerV1 {
let relative_pointer = relative_pointer_manager.get_relative_pointer(&*pointer);
let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer);
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_relative_pointer(event, winit_state);
@@ -255,3 +328,16 @@ pub(super) fn init_confined_pointer(
confined_pointer.detach()
}
pub(super) fn init_locked_pointer(
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
surface: &WlSurface,
pointer: &WlPointer,
) -> ZwpLockedPointerV1 {
let locked_pointer =
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
locked_pointer.quick_assign(move |_, _, _| {});
locked_pointer.detach()
}

View File

@@ -5,11 +5,11 @@ use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input
Event as TextInputEvent, ZwpTextInputV3,
};
use crate::event::WindowEvent;
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::event_loop::WinitState;
use super::{TextInputHandler, TextInputInner};
use super::{Preedit, TextInputHandler, TextInputInner};
#[inline]
pub(super) fn handle_text_input(
@@ -30,8 +30,11 @@ pub(super) fn handle_text_input(
inner.target_window_id = Some(window_id);
// Enable text input on that surface.
text_input.enable();
text_input.commit();
if window_handle.ime_allowed.get() {
text_input.enable();
text_input.commit();
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
// Notify a window we're currently over about text input handler.
let text_input_handler = TextInputHandler {
@@ -58,19 +61,45 @@ pub(super) fn handle_text_input(
text_input: text_input.detach(),
};
window_handle.text_input_left(text_input_handler);
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
}
TextInputEvent::PreeditString {
text,
cursor_begin,
cursor_end,
} => {
let cursor_begin = usize::try_from(cursor_begin).ok();
let cursor_end = usize::try_from(cursor_end).ok();
let text = text.unwrap_or_default();
inner.pending_preedit = Some(Preedit {
text,
cursor_begin,
cursor_end,
});
}
TextInputEvent::CommitString { text } => {
// Update currenly commited string.
inner.commit_string = text;
// Update currenly commited string and reset previous preedit.
inner.pending_preedit = None;
inner.pending_commit = Some(text.unwrap_or_default());
}
TextInputEvent::Done { .. } => {
let (window_id, text) = match (inner.target_window_id, inner.commit_string.take()) {
(Some(window_id), Some(text)) => (window_id, text),
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
_ => return,
};
for ch in text.chars() {
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
if let Some(text) = inner.pending_commit.take() {
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Push preedit string we've got after latest commit.
if let Some(preedit) = inner.pending_preedit.take() {
let cursor_range = preedit
.cursor_begin
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
let event = Ime::Preedit(preedit.text, cursor_range);
event_sink.push_window_event(WindowEvent::Ime(event), window_id);
}
}
_ => (),

View File

@@ -20,6 +20,17 @@ impl TextInputHandler {
self.text_input.set_cursor_rectangle(x, y, 0, 0);
self.text_input.commit();
}
#[inline]
pub fn set_input_allowed(&self, allowed: bool) {
if allowed {
self.text_input.enable();
} else {
self.text_input.disable();
}
self.text_input.commit();
}
}
/// A wrapper around text input to automatically destroy the object on `Drop`.
@@ -52,15 +63,25 @@ struct TextInputInner {
/// Currently focused surface.
target_window_id: Option<WindowId>,
/// Pending string to commit.
commit_string: Option<String>,
/// Pending commit event which will be dispatched on `text_input_v3::Done`.
pending_commit: Option<String>,
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
pending_preedit: Option<Preedit>,
}
struct Preedit {
text: String,
cursor_begin: Option<usize>,
cursor_end: Option<usize>,
}
impl TextInputInner {
fn new() -> Self {
Self {
target_window_id: None,
commit_string: None,
pending_commit: None,
pending_preedit: None,
}
}
}

View File

@@ -44,9 +44,15 @@ pub(super) fn handle_touch(
window_id,
);
inner
.touch_points
.push(TouchPoint::new(surface, position, id));
// For `TouchEvent::Up` we don't receive a position, so we're tracking active
// touch points. Update either a known touch id or register a new one.
if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) {
inner.touch_points[i].position = position;
} else {
inner
.touch_points
.push(TouchPoint::new(surface, position, id));
}
}
TouchEvent::Up { id, .. } => {
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {

View File

@@ -7,8 +7,10 @@ use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use raw_window_handle::WaylandHandle;
use sctk::window::{Decorations, FallbackFrame};
use raw_window_handle::{
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
};
use sctk::window::Decorations;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
@@ -17,7 +19,9 @@ use crate::platform_impl::{
MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes};
use crate::window::{
CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes,
};
use super::env::WindowingFeatures;
use super::event_loop::WinitState;
@@ -28,6 +32,14 @@ pub mod shim;
use shim::{WindowHandle, WindowRequest, WindowUpdate};
#[cfg(feature = "sctk-adwaita")]
pub type WinitFrame = sctk_adwaita::AdwaitaFrame;
#[cfg(not(feature = "sctk-adwaita"))]
pub type WinitFrame = sctk::window::FallbackFrame;
#[cfg(feature = "sctk-adwaita")]
const WAYLAND_CSD_THEME_ENV_VAR: &str = "WINIT_WAYLAND_CSD_THEME";
pub struct Window {
/// Window id.
window_id: WindowId,
@@ -58,10 +70,19 @@ pub struct Window {
/// Requests that SCTK window should perform.
window_requests: Arc<Mutex<Vec<WindowRequest>>>,
/// Whether the window is resizeable.
resizeable: AtomicBool,
/// Whether the window is decorated.
decorated: AtomicBool,
/// Grabbing mode.
cursor_grab_mode: Mutex<CursorGrabMode>,
}
impl Window {
pub fn new<T>(
pub(crate) fn new<T>(
event_loop_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
platform_attributes: PlatformAttributes,
@@ -71,7 +92,7 @@ impl Window {
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
// Get the window that receiced the event.
// Get the window that received the event.
let window_id = super::make_wid(&surface);
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
@@ -87,7 +108,7 @@ impl Window {
let window_id = super::make_wid(&surface);
let maximized = Arc::new(AtomicBool::new(false));
let maximzied_clone = maximized.clone();
let maximized_clone = maximized.clone();
let fullscreen = Arc::new(AtomicBool::new(false));
let fullscreen_clone = fullscreen.clone();
@@ -99,7 +120,7 @@ impl Window {
let theme_manager = event_loop_window_target.theme_manager.clone();
let mut window = event_loop_window_target
.env
.create_window::<FallbackFrame, _>(
.create_window::<WinitFrame, _>(
surface.clone(),
Some(theme_manager),
(width, height),
@@ -115,7 +136,7 @@ impl Window {
}
Event::Configure { new_size, states } => {
let is_maximized = states.contains(&State::Maximized);
maximzied_clone.store(is_maximized, Ordering::Relaxed);
maximized_clone.store(is_maximized, Ordering::Relaxed);
let is_fullscreen = states.contains(&State::Fullscreen);
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
@@ -133,18 +154,26 @@ impl Window {
)
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
// Set CSD frame config
#[cfg(feature = "sctk-adwaita")]
{
let theme = platform_attributes.csd_theme.unwrap_or_else(|| {
let env = std::env::var(WAYLAND_CSD_THEME_ENV_VAR).unwrap_or_default();
match env.to_lowercase().as_str() {
"dark" => Theme::Dark,
_ => Theme::Light,
}
});
window.set_frame_config(theme.into());
}
// Set decorations.
if attributes.decorations {
window.set_decorate(Decorations::FollowServer);
} else {
window.set_decorate(Decorations::None);
}
// Without this commit here at least on kwin 5.23.3 the initial configure
// will have a size (1,1), the second configure including the decoration
// mode will have the min_size as its size. With this commit the initial
// configure will have no size, the application will draw it's content
// with the initial size and everything works as expected afterwards.
window.surface().commit();
// Min dimensions.
let min_size = attributes
@@ -159,8 +188,8 @@ impl Window {
window.set_max_size(max_size);
// Set Wayland specific window attributes.
if let Some(app_id) = platform_attributes.app_id {
window.set_app_id(app_id);
if let Some(name) = platform_attributes.name {
window.set_app_id(name.general);
}
// Set common window attributes.
@@ -192,6 +221,16 @@ impl Window {
}
}
// Without this commit here at least on kwin 5.23.3 the initial configure
// will have a size (1,1), the second configure including the decoration
// mode will have the min_size as its size. With this commit the initial
// configure will have no size, the application will draw it's content
// with the initial size and everything works as expected afterwards.
//
// The window commit must be after setting on top level properties, but right before any
// buffer attachments commits.
window.surface().commit();
let size = Arc::new(Mutex::new(LogicalSize::new(width, height)));
// We should trigger redraw and commit the surface for the newly created window.
@@ -210,10 +249,19 @@ impl Window {
window_requests.clone(),
);
// Set resizable state, so we can determine how to handle `Window::set_inner_size`.
window_handle.is_resizable.set(attributes.resizable);
let mut winit_state = event_loop_window_target.state.borrow_mut();
winit_state.window_map.insert(window_id, window_handle);
// On Wayland window doesn't have Focus by default and it'll get it later on. So be
// explicit here.
winit_state
.event_sink
.push_window_event(crate::event::WindowEvent::Focused(false), window_id);
winit_state
.window_updates
.insert(window_id, WindowUpdate::new());
@@ -248,6 +296,9 @@ impl Window {
fullscreen,
maximized,
windowing_features,
resizeable: AtomicBool::new(attributes.resizable),
decorated: AtomicBool::new(attributes.decorations),
cursor_grab_mode: Mutex::new(CursorGrabMode::None),
};
Ok(window)
@@ -270,6 +321,11 @@ impl Window {
// Not possible on Wayland.
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
None
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
@@ -333,9 +389,15 @@ impl Window {
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.resizeable.store(resizable, Ordering::Relaxed);
self.send_request(WindowRequest::Resizeable(resizable));
}
#[inline]
pub fn is_resizable(&self) -> bool {
self.resizeable.load(Ordering::Relaxed)
}
#[inline]
pub fn scale_factor(&self) -> u32 {
// The scale factor from `get_surface_scale_factor` is always greater than zero, so
@@ -345,9 +407,20 @@ impl Window {
#[inline]
pub fn set_decorations(&self, decorate: bool) {
self.decorated.store(decorate, Ordering::Relaxed);
self.send_request(WindowRequest::Decorate(decorate));
}
#[inline]
pub fn is_decorated(&self) -> bool {
self.decorated.load(Ordering::Relaxed)
}
#[inline]
pub fn set_csd_theme(&self, theme: Theme) {
self.send_request(WindowRequest::CsdThemeVariant(theme));
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
// You can't unminimize the window on Wayland.
@@ -415,12 +488,17 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
if !self.windowing_features.cursor_grab() {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
if !self.windowing_features.pointer_constraints() {
if mode == CursorGrabMode::None {
return Ok(());
}
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
self.send_request(WindowRequest::GrabCursor(grab));
*self.cursor_grab_mode.lock().unwrap() = mode;
self.send_request(WindowRequest::SetCursorGrabMode(mode));
Ok(())
}
@@ -435,15 +513,19 @@ impl Window {
}
#[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> {
// XXX This is possible if the locked pointer is being used. We don't have any
// API for that right now, but it could be added in
// https://github.com/rust-windowing/winit/issues/1677.
//
// This function is essential for the locked pointer API.
//
// See pointer-constraints-unstable-v1.xml.
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
// Positon can be set only for locked cursor.
if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked {
return Err(ExternalError::Os(os_error!(OsError::WaylandMisc(
"cursor position can be set only for locked cursor."
))));
}
let scale_factor = self.scale_factor() as f64;
let position = position.to_logical(scale_factor);
self.send_request(WindowRequest::SetLockedCursorPosition(position));
Ok(())
}
#[inline]
@@ -453,11 +535,23 @@ impl Window {
Ok(())
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
self.send_request(WindowRequest::PassthroughMouseInput(!hittest));
Ok(())
}
#[inline]
pub fn set_ime_position(&self, position: Position) {
let scale_factor = self.scale_factor() as f64;
let position = position.to_logical(scale_factor);
self.send_request(WindowRequest::IMEPosition(position));
self.send_request(WindowRequest::ImePosition(position));
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
self.send_request(WindowRequest::AllowIme(allowed));
}
#[inline]
@@ -487,11 +581,17 @@ impl Window {
}
#[inline]
pub fn raw_window_handle(&self) -> WaylandHandle {
let mut handle = WaylandHandle::empty();
handle.display = self.display.get_display_ptr() as *mut _;
handle.surface = self.surface.as_ref().c_ptr() as *mut _;
handle
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = WaylandWindowHandle::empty();
window_handle.surface = self.surface.as_ref().c_ptr() as *mut _;
RawWindowHandle::Wayland(window_handle)
}
#[inline]
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)
}
#[inline]
@@ -506,3 +606,13 @@ impl Drop for Window {
self.send_request(WindowRequest::Close);
}
}
#[cfg(feature = "sctk-adwaita")]
impl From<Theme> for sctk_adwaita::FrameConfig {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => sctk_adwaita::FrameConfig::light(),
Theme::Dark => sctk_adwaita::FrameConfig::dark(),
}
}
}

View File

@@ -1,24 +1,28 @@
use std::cell::Cell;
use std::mem::ManuallyDrop;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::Environment;
use sctk::window::{Decorations, FallbackFrame, Window};
use sctk::window::{Decorations, Window};
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::WindowEvent;
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::env::WinitEnv;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
use crate::platform_impl::wayland::WindowId;
use crate::window::{CursorIcon, UserAttentionType};
use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType};
use super::WinitFrame;
/// A request to SCTK window from Winit window.
#[derive(Debug, Clone)]
@@ -37,8 +41,11 @@ pub enum WindowRequest {
/// Change the cursor icon.
NewCursorIcon(CursorIcon),
/// Grab cursor.
GrabCursor(bool),
/// Change cursor grabbing mode.
SetCursorGrabMode(CursorGrabMode),
/// Set cursor position.
SetLockedCursorPosition(LogicalPosition<u32>),
/// Drag window.
DragWindow,
@@ -52,6 +59,9 @@ pub enum WindowRequest {
/// Request decorations change.
Decorate(bool),
/// Request decorations change.
CsdThemeVariant(Theme),
/// Make the window resizeable.
Resizeable(bool),
@@ -68,13 +78,19 @@ pub enum WindowRequest {
FrameSize(LogicalSize<u32>),
/// Set IME window position.
IMEPosition(LogicalPosition<u32>),
ImePosition(LogicalPosition<u32>),
/// Enable IME on the given window.
AllowIme(bool),
/// Request Attention.
///
/// `None` unsets the attention request.
Attention(Option<UserAttentionType>),
/// Passthrough mouse input to underlying windows.
PassthroughMouseInput(bool),
/// Redraw was requested.
Redraw,
@@ -139,7 +155,7 @@ impl WindowUpdate {
/// and react to events.
pub struct WindowHandle {
/// An actual window.
pub window: Window<FallbackFrame>,
pub window: ManuallyDrop<Window<WinitFrame>>,
/// The current size of the window.
pub size: Arc<Mutex<LogicalSize<u32>>>,
@@ -150,11 +166,17 @@ pub struct WindowHandle {
/// Current cursor icon.
pub cursor_icon: Cell<CursorIcon>,
/// Whether the window is resizable.
pub is_resizable: Cell<bool>,
/// Allow IME events for that window.
pub ime_allowed: Cell<bool>,
/// Visible cursor or not.
cursor_visible: Cell<bool>,
/// Cursor confined to the surface.
confined: Cell<bool>,
cursor_grab_mode: Cell<CursorGrabMode>,
/// Pointers over the current surface.
pointers: Vec<WinitPointer>,
@@ -167,49 +189,71 @@ pub struct WindowHandle {
/// Indicator whether user attention is requested.
attention_requested: Cell<bool>,
/// Compositor
compositor: Attached<WlCompositor>,
}
impl WindowHandle {
pub fn new(
env: &Environment<WinitEnv>,
window: Window<FallbackFrame>,
window: Window<WinitFrame>,
size: Arc<Mutex<LogicalSize<u32>>>,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self {
let xdg_activation = env.get_global::<XdgActivationV1>();
// Unwrap is safe, since we can't create window without compositor anyway and won't be
// here.
let compositor = env.get_global::<WlCompositor>().unwrap();
Self {
window,
window: ManuallyDrop::new(window),
size,
pending_window_requests,
cursor_icon: Cell::new(CursorIcon::Default),
confined: Cell::new(false),
is_resizable: Cell::new(true),
cursor_grab_mode: Cell::new(CursorGrabMode::None),
cursor_visible: Cell::new(true),
pointers: Vec::new(),
text_inputs: Vec::new(),
xdg_activation,
attention_requested: Cell::new(false),
compositor,
ime_allowed: Cell::new(false),
}
}
pub fn set_cursor_grab(&self, grab: bool) {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
// The new requested state matches the current confine status, return.
if self.confined.get() == grab {
let old_mode = self.cursor_grab_mode.replace(mode);
if old_mode == mode {
return;
}
self.confined.replace(grab);
// Clear old pointer data.
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
}
for pointer in self.pointers.iter() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(surface);
} else {
pointer.unconfine();
let surface = self.window.surface();
match mode {
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
CursorGrabMode::None => {
// Current lock/confine was already removed.
}
}
}
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
// XXX the cursor locking is ensured inside `Window`.
self.pointers
.iter()
.for_each(|p| p.set_cursor_position(position.x, position.y));
}
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
None => return,
@@ -257,10 +301,13 @@ impl WindowHandle {
let position = self.pointers.iter().position(|p| *p == pointer);
if position.is_none() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(surface);
let surface = self.window.surface();
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.lock(surface),
CursorGrabMode::Confined => pointer.confine(surface),
}
self.pointers.push(pointer);
}
@@ -275,9 +322,11 @@ impl WindowHandle {
if let Some(position) = position {
let pointer = self.pointers.remove(position);
// Drop the confined pointer.
if self.confined.get() {
pointer.unconfine();
// Drop the grabbing mode.
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.unlock(),
CursorGrabMode::Confined => pointer.unconfine(),
}
}
}
@@ -304,6 +353,41 @@ impl WindowHandle {
}
}
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
if passthrough_mouse_input {
let region = self.compositor.create_region();
region.add(0, 0, 0, 0);
self.window
.surface()
.set_input_region(Some(&region.detach()));
region.destroy();
} else {
// Using `None` results in the entire window being clickable.
self.window.surface().set_input_region(None);
}
}
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
if self.ime_allowed.get() == allowed {
return;
}
self.ime_allowed.replace(allowed);
let window_id = wayland::make_wid(self.window.surface());
for text_input in self.text_inputs.iter() {
text_input.set_input_allowed(allowed);
}
let event = if allowed {
WindowEvent::Ime(Ime::Enabled)
} else {
WindowEvent::Ime(Ime::Disabled)
};
event_sink.push_window_event(event, window_id);
}
pub fn set_cursor_visible(&self, visible: bool) {
self.cursor_visible.replace(visible);
let cursor_icon = match visible {
@@ -344,7 +428,8 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
// Process the rest of the events.
for (window_id, window_handle) in window_map.iter_mut() {
let mut requests = window_handle.pending_window_requests.lock().unwrap();
for request in requests.drain(..) {
let requests = requests.drain(..);
for request in requests {
match request {
WindowRequest::Fullscreen(fullscreen) => {
window_handle.window.set_fullscreen(fullscreen.as_ref());
@@ -358,11 +443,18 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
WindowRequest::NewCursorIcon(cursor_icon) => {
window_handle.set_cursor_icon(cursor_icon);
}
WindowRequest::IMEPosition(position) => {
WindowRequest::ImePosition(position) => {
window_handle.set_ime_position(position);
}
WindowRequest::GrabCursor(grab) => {
window_handle.set_cursor_grab(grab);
WindowRequest::AllowIme(allow) => {
let event_sink = &mut winit_state.event_sink;
window_handle.set_ime_allowed(allow, event_sink);
}
WindowRequest::SetCursorGrabMode(mode) => {
window_handle.set_cursor_grab(mode);
}
WindowRequest::SetLockedCursorPosition(position) => {
window_handle.set_locked_cursor_position(position);
}
WindowRequest::DragWindow => {
window_handle.drag_window();
@@ -389,6 +481,15 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
#[cfg(feature = "sctk-adwaita")]
WindowRequest::CsdThemeVariant(theme) => {
window_handle.window.set_frame_config(theme.into());
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
#[cfg(not(feature = "sctk-adwaita"))]
WindowRequest::CsdThemeVariant(_) => {}
WindowRequest::Resizeable(resizeable) => {
window_handle.window.set_resizable(resizeable);
@@ -418,13 +519,26 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
window_update.redraw_requested = true;
}
WindowRequest::FrameSize(size) => {
// Set new size.
if !window_handle.is_resizable.get() {
// On Wayland non-resizable window is achieved by setting both min and max
// size of the window to the same value.
let size = Some((size.width, size.height));
window_handle.window.set_max_size(size);
window_handle.window.set_min_size(size);
}
window_handle.window.resize(size.width, size.height);
// We should refresh the frame after resize.
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::PassthroughMouseInput(passthrough) => {
window_handle.passthrough_mouse_input(passthrough);
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Attention(request_type) => {
window_handle.set_user_attention(request_type);
}
@@ -450,3 +564,14 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
let _ = window_updates.remove(&window);
}
}
impl Drop for WindowHandle {
fn drop(&mut self) {
unsafe {
let surface = self.window.surface().clone();
// The window must be destroyed before wl_surface.
ManuallyDrop::drop(&mut self.window);
surface.destroy();
}
}
}

View File

@@ -12,20 +12,23 @@ use super::{
use util::modifiers::{ModifierKeyState, ModifierKeymap};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent,
DeviceEvent, ElementState, Event, Ime, KeyboardInput, ModifiersState, TouchPhase,
WindowEvent,
},
event_loop::EventLoopWindowTarget as RootELW,
};
/// The X11 documentation states: "Keycodes lie in the inclusive range [8,255]".
/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`".
const KEYCODE_OFFSET: u8 = 8;
pub(super) struct EventProcessor<T: 'static> {
pub(super) dnd: Dnd,
pub(super) ime_receiver: ImeReceiver,
pub(super) ime_event_receiver: ImeEventReceiver,
pub(super) randr_event_offset: c_int,
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension,
@@ -37,6 +40,7 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) first_touch: Option<u64>,
// Currently focused window belonging to this process
pub(super) active_window: Option<ffi::Window>,
pub(super) is_composing: bool,
}
impl<T: 'static> EventProcessor<T> {
@@ -45,7 +49,7 @@ impl<T: 'static> EventProcessor<T> {
let mut devices = self.devices.borrow_mut();
if let Some(info) = DeviceInfo::get(&wt.xconn, device) {
for info in info.iter() {
devices.insert(DeviceId(info.deviceid), Device::new(&self, info));
devices.insert(DeviceId(info.deviceid), Device::new(info));
}
}
}
@@ -55,7 +59,7 @@ impl<T: 'static> EventProcessor<T> {
F: Fn(&Arc<UnownedWindow>) -> Ret,
{
let mut deleted = false;
let window_id = WindowId(window_id);
let window_id = WindowId(window_id as u64);
let wt = get_xtarget(&self.target);
let result = wt
.windows
@@ -410,7 +414,7 @@ impl<T: 'static> EventProcessor<T> {
// resizing by dragging across monitors *without* dropping the window.
let (width, height) = shared_state_lock
.dpi_adjusted
.unwrap_or_else(|| (xev.width as u32, xev.height as u32));
.unwrap_or((xev.width as u32, xev.height as u32));
let last_scale_factor = shared_state_lock.last_monitor.scale_factor;
let new_scale_factor = {
@@ -509,7 +513,7 @@ impl<T: 'static> EventProcessor<T> {
// In the event that the window's been destroyed without being dropped first, we
// cleanup again here.
wt.windows.borrow_mut().remove(&WindowId(window));
wt.windows.borrow_mut().remove(&WindowId(window as u64));
// Since all XIM stuff needs to happen from the same thread, we destroy the input
// context here instead of when dropping the window.
@@ -527,8 +531,13 @@ impl<T: 'static> EventProcessor<T> {
ffi::VisibilityNotify => {
let xev: &ffi::XVisibilityEvent = xev.as_ref();
let xwindow = xev.window;
self.with_window(xwindow, |window| window.visibility_notify());
callback(Event::WindowEvent {
window_id: mkwid(xwindow),
event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured),
});
self.with_window(xwindow, |window| {
window.visibility_notify();
});
}
ffi::Expose => {
@@ -567,7 +576,7 @@ impl<T: 'static> EventProcessor<T> {
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
// a keycode of 0.
if keycode != 0 {
if keycode != 0 && !self.is_composing {
let scancode = keycode - KEYCODE_OFFSET as u32;
let keysym = wt.xconn.lookup_keysym(xkev);
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
@@ -602,12 +611,25 @@ impl<T: 'static> EventProcessor<T> {
return;
};
for chr in written.chars() {
// If we're composing right now, send the string we've got from X11 via
// Ime::Commit.
if self.is_composing && keycode == 0 && !written.is_empty() {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(chr),
event: WindowEvent::Ime(Ime::Commit(written)),
};
self.is_composing = false;
callback(event);
} else {
for chr in written.chars() {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(chr),
};
callback(event);
}
}
}
}
@@ -693,8 +715,8 @@ impl<T: 'static> EventProcessor<T> {
delta: match xev.detail {
4 => LineDelta(0.0, 1.0),
5 => LineDelta(0.0, -1.0),
6 => LineDelta(-1.0, 0.0),
7 => LineDelta(1.0, 0.0),
6 => LineDelta(1.0, 0.0),
7 => LineDelta(-1.0, 0.0),
_ => unreachable!(),
},
phase: TouchPhase::Moved,
@@ -774,10 +796,10 @@ impl<T: 'static> EventProcessor<T> {
event: MouseWheel {
device_id,
delta: match info.orientation {
ScrollOrientation::Horizontal => {
LineDelta(delta as f32, 0.0)
}
// X11 vertical scroll coordinates are opposite to winit's
ScrollOrientation::Horizontal => {
LineDelta(-delta as f32, 0.0)
}
ScrollOrientation::Vertical => {
LineDelta(0.0, -delta as f32)
}
@@ -890,6 +912,8 @@ impl<T: 'static> EventProcessor<T> {
if self.active_window != Some(xev.event) {
self.active_window = Some(xev.event);
wt.update_device_event_filter(true);
let window_id = mkwid(xev.event);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
@@ -925,7 +949,7 @@ impl<T: 'static> EventProcessor<T> {
// Issue key press events for all pressed keys
Self::handle_pressed_keys(
&wt,
wt,
window_id,
ElementState::Pressed,
&self.mod_keymap,
@@ -939,6 +963,7 @@ impl<T: 'static> EventProcessor<T> {
if !self.window_exists(xev.event) {
return;
}
wt.ime
.borrow_mut()
.unfocus(xev.event)
@@ -947,9 +972,11 @@ impl<T: 'static> EventProcessor<T> {
if self.active_window.take() == Some(xev.event) {
let window_id = mkwid(xev.event);
wt.update_device_event_filter(false);
// Issue key release events for all pressed keys
Self::handle_pressed_keys(
&wt,
wt,
window_id,
ElementState::Released,
&self.mod_keymap,
@@ -1179,20 +1206,19 @@ impl<T: 'static> EventProcessor<T> {
if monitor.name == new_monitor.name {
let (width, height) = window.inner_size_physical();
let (new_width, new_height) = window.adjust_for_dpi(
// If there all monitors are closed before, scale
// factor would be already changed to 1.0.
maybe_prev_scale_factor.unwrap_or(1.0),
// If we couldn't determine the previous scale
// factor (e.g., because all monitors were closed
// before), just pick whatever the current monitor
// has set as a baseline.
maybe_prev_scale_factor
.unwrap_or(monitor.scale_factor),
new_monitor.scale_factor,
width,
height,
&*window.shared_state.lock(),
);
let window_id = crate::window::WindowId(
crate::platform_impl::platform::WindowId::X(
*window_id,
),
);
let window_id = crate::window::WindowId(*window_id);
let old_inner_size = PhysicalSize::new(width, height);
let mut new_inner_size =
PhysicalSize::new(new_width, new_height);
@@ -1220,11 +1246,61 @@ impl<T: 'static> EventProcessor<T> {
}
}
match self.ime_receiver.try_recv() {
Ok((window_id, x, y)) => {
wt.ime.borrow_mut().send_xim_spot(window_id, x, y);
// Handle IME requests.
if let Ok(request) = self.ime_receiver.try_recv() {
let mut ime = wt.ime.borrow_mut();
match request {
ImeRequest::Position(window_id, x, y) => {
ime.send_xim_spot(window_id, x, y);
}
ImeRequest::Allow(window_id, allowed) => {
ime.set_ime_allowed(window_id, allowed);
}
}
}
let (window, event) = match self.ime_event_receiver.try_recv() {
Ok((window, event)) => (window, event),
Err(_) => return,
};
match event {
ImeEvent::Enabled => {
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Enabled),
});
}
ImeEvent::Start => {
self.is_composing = true;
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)),
});
}
ImeEvent::Update(text, position) => {
if self.is_composing {
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))),
});
}
}
ImeEvent::End => {
self.is_composing = false;
// Issue empty preedit on `Done`.
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Preedit(String::new(), None)),
});
}
ImeEvent::Disabled => {
self.is_composing = false;
callback(Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::Ime(Ime::Disabled),
});
}
Err(_) => (),
}
}

View File

@@ -9,7 +9,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
//ffi::XK_Linefeed => VirtualKeyCode::Linefeed,
//ffi::XK_Clear => VirtualKeyCode::Clear,
ffi::XK_Return => VirtualKeyCode::Return,
//ffi::XK_Pause => VirtualKeyCode::Pause,
ffi::XK_Pause => VirtualKeyCode::Pause,
//ffi::XK_Scroll_Lock => VirtualKeyCode::Scroll_lock,
//ffi::XK_Sys_Req => VirtualKeyCode::Sys_req,
ffi::XK_Escape => VirtualKeyCode::Escape,
@@ -59,10 +59,10 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
//ffi::XK_Break => VirtualKeyCode::Break,
//ffi::XK_Mode_switch => VirtualKeyCode::Mode_switch,
//ffi::XK_script_switch => VirtualKeyCode::Script_switch,
//ffi::XK_Num_Lock => VirtualKeyCode::Num_lock,
ffi::XK_Num_Lock => VirtualKeyCode::Numlock,
//ffi::XK_KP_Space => VirtualKeyCode::Kp_space,
//ffi::XK_KP_Tab => VirtualKeyCode::Kp_tab,
//ffi::XK_KP_Enter => VirtualKeyCode::Kp_enter,
ffi::XK_KP_Enter => VirtualKeyCode::NumpadEnter,
//ffi::XK_KP_F1 => VirtualKeyCode::Kp_f1,
//ffi::XK_KP_F2 => VirtualKeyCode::Kp_f2,
//ffi::XK_KP_F3 => VirtualKeyCode::Kp_f3,
@@ -83,7 +83,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals,
ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply,
ffi::XK_KP_Add => VirtualKeyCode::NumpadAdd,
//ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator,
ffi::XK_KP_Separator => VirtualKeyCode::NumpadComma,
ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract,
ffi::XK_KP_Decimal => VirtualKeyCode::NumpadDecimal,
ffi::XK_KP_Divide => VirtualKeyCode::NumpadDivide,
@@ -161,7 +161,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
ffi::XK_Shift_R => VirtualKeyCode::RShift,
ffi::XK_Control_L => VirtualKeyCode::LControl,
ffi::XK_Control_R => VirtualKeyCode::RControl,
//ffi::XK_Caps_Lock => VirtualKeyCode::Caps_lock,
ffi::XK_Caps_Lock => VirtualKeyCode::Capital,
//ffi::XK_Shift_Lock => VirtualKeyCode::Shift_lock,
//ffi::XK_Meta_L => VirtualKeyCode::Meta_l,
//ffi::XK_Meta_R => VirtualKeyCode::Meta_r,

View File

@@ -62,7 +62,7 @@ pub unsafe fn set_destroy_callback(
inner: &ImeInner,
) -> Result<(), XError> {
xim_set_callback(
&xconn,
xconn,
im,
ffi::XNDestroyCallback_0.as_ptr() as *const _,
&inner.destroy_callback as *const _ as *mut _,
@@ -70,6 +70,7 @@ pub unsafe fn set_destroy_callback(
}
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
enum ReplaceImError {
MethodOpenFailed(PotentialInputMethods),
ContextCreationFailed(ImeContextCreationError),
@@ -107,8 +108,19 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let mut new_contexts = HashMap::new();
for (window, old_context) in (*inner).contexts.iter() {
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
let is_allowed = old_context
.as_ref()
.map(|old_context| old_context.is_allowed)
.unwrap_or_default();
let new_context = {
let result = ImeContext::new(xconn, new_im.im, *window, spot);
let result = ImeContext::new(
xconn,
new_im.im,
*window,
spot,
is_allowed,
(*inner).event_sender.clone(),
);
if result.is_err() {
let _ = close_im(xconn, new_im.im);
}
@@ -136,13 +148,17 @@ pub unsafe extern "C" fn xim_instantiate_callback(
let inner: *mut ImeInner = client_data as _;
if !inner.is_null() {
let xconn = &(*inner).xconn;
let result = replace_im(inner);
if result.is_ok() {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
} else if result.is_err() && (*inner).is_destroyed {
// We have no usable input methods!
result.expect("Failed to reopen input method");
match replace_im(inner) {
Ok(()) => {
let _ = unset_instantiate_callback(xconn, client_data);
(*inner).is_fallback = false;
}
Err(err) => {
if (*inner).is_destroyed {
// We have no usable input methods!
panic!("Failed to reopen input method: {:?}", err);
}
}
}
}
}
@@ -163,12 +179,12 @@ pub unsafe extern "C" fn xim_destroy_callback(
if !(*inner).is_fallback {
let _ = set_instantiate_callback(xconn, client_data);
// Attempt to open fallback input method.
let result = replace_im(inner);
if result.is_ok() {
(*inner).is_fallback = true;
} else {
// We have no usable input methods!
result.expect("Failed to open fallback input method");
match replace_im(inner) {
Ok(()) => (*inner).is_fallback = true,
Err(err) => {
// We have no usable input methods!
panic!("Failed to open fallback input method: {:?}", err);
}
}
}
}

View File

@@ -1,41 +1,202 @@
use std::{
os::raw::{c_short, c_void},
ptr,
sync::Arc,
};
use std::ffi::CStr;
use std::os::raw::c_short;
use std::sync::Arc;
use std::{mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
use super::{ffi, util, XConnection, XError};
/// IME creation error.
#[derive(Debug)]
pub enum ImeContextCreationError {
/// Got the error from Xlib.
XError(XError),
/// Got null pointer from Xlib but without exact reason.
Null,
}
unsafe fn create_pre_edit_attr<'a>(
xconn: &'a Arc<XConnection>,
ic_spot: &'a ffi::XPoint,
) -> util::XSmartPointer<'a, c_void> {
util::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNSpotLocation_0.as_ptr() as *const _,
ic_spot,
ptr::null_mut::<()>(),
),
)
.expect("XVaCreateNestedList returned NULL")
/// The callback used by XIM preedit functions.
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
/// Wrapper for creating XIM callbacks.
#[inline]
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
XIMCallback {
client_data,
callback: Some(callback),
}
}
// WARNING: this struct doesn't destroy its XIC resource when dropped.
/// The server started preedit.
extern "C" fn preedit_start_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
_call_data: ffi::XPointer,
) -> i32 {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
client_data.text.clear();
client_data.cursor_pos = 0;
client_data
.event_sender
.send((client_data.window, ImeEvent::Start))
.expect("failed to send preedit start event");
-1
}
/// Done callback is used when the preedit should be hidden.
extern "C" fn preedit_done_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
_call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
// Drop text buffer and reset cursor position on done.
client_data.text = Vec::new();
client_data.cursor_pos = 0;
client_data
.event_sender
.send((client_data.window, ImeEvent::End))
.expect("failed to send preedit end event");
}
fn calc_byte_position(text: &[char], pos: usize) -> usize {
text.iter()
.take(pos)
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
}
/// Preedit text information to be drawn inline by the client.
extern "C" fn preedit_draw_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
client_data.cursor_pos = call_data.caret as usize;
let chg_range =
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
warn!(
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
client_data.text.len(),
call_data.chg_first,
call_data.chg_length
);
return;
}
// NULL indicate text deletion
let mut new_chars = if call_data.text.is_null() {
Vec::new()
} else {
let xim_text = unsafe { &mut *(call_data.text) };
if xim_text.encoding_is_wchar > 0 {
return;
}
let new_text = unsafe { xim_text.string.multi_byte };
if new_text.is_null() {
return;
}
let new_text = unsafe { CStr::from_ptr(new_text) };
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
.chars()
.collect()
};
let mut old_text_tail = client_data.text.split_off(chg_range.end);
client_data.text.truncate(chg_range.start);
client_data.text.append(&mut new_chars);
client_data.text.append(&mut old_text_tail);
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
client_data
.event_sender
.send((
client_data.window,
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
))
.expect("failed to send preedit update event");
}
/// Handling of cursor movements in preedit text.
extern "C" fn preedit_caret_callback(
_xim: ffi::XIM,
client_data: ffi::XPointer,
call_data: ffi::XPointer,
) {
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
client_data.cursor_pos = call_data.position as usize;
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
client_data
.event_sender
.send((
client_data.window,
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
))
.expect("failed to send preedit update event");
}
}
/// Struct to simplify callback creation and latter passing into Xlib XIM.
struct PreeditCallbacks {
start_callback: ffi::XIMCallback,
done_callback: ffi::XIMCallback,
draw_callback: ffi::XIMCallback,
caret_callback: ffi::XIMCallback,
}
impl PreeditCallbacks {
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
let start_callback = create_xim_callback(client_data, unsafe {
mem::transmute(preedit_start_callback as usize)
});
let done_callback = create_xim_callback(client_data, preedit_done_callback);
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
PreeditCallbacks {
start_callback,
done_callback,
caret_callback,
draw_callback,
}
}
}
struct ImeContextClientData {
window: ffi::Window,
event_sender: ImeEventSender,
text: Vec<char>,
cursor_pos: usize,
}
// XXX: this struct doesn't destroy its XIC resource when dropped.
// This is intentional, as it doesn't have enough information to know whether or not the context
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
// through `ImeInner`.
#[derive(Debug)]
pub struct ImeContext {
pub ic: ffi::XIC,
pub ic_spot: ffi::XPoint,
pub(super) ic: ffi::XIC,
pub(super) ic_spot: ffi::XPoint,
pub(super) is_allowed: bool,
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
// there we keep the pointer to automatically deallocate it.
_client_data: Box<ImeContextClientData>,
}
impl ImeContext {
@@ -44,66 +205,111 @@ impl ImeContext {
im: ffi::XIM,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
is_allowed: bool,
event_sender: ImeEventSender,
) -> Result<Self, ImeContextCreationError> {
let ic = if let Some(ic_spot) = ic_spot {
ImeContext::create_ic_with_spot(xconn, im, window, ic_spot)
let client_data = Box::into_raw(Box::new(ImeContextClientData {
window,
event_sender,
text: Vec::new(),
cursor_pos: 0,
}));
let ic = if is_allowed {
ImeContext::create_ic(xconn, im, window, client_data as ffi::XPointer)
.ok_or(ImeContextCreationError::Null)?
} else {
ImeContext::create_ic(xconn, im, window)
ImeContext::create_none_ic(xconn, im, window).ok_or(ImeContextCreationError::Null)?
};
let ic = ic.ok_or(ImeContextCreationError::Null)?;
xconn
.check_errors()
.map_err(ImeContextCreationError::XError)?;
Ok(ImeContext {
let mut context = ImeContext {
ic,
ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }),
})
ic_spot: ffi::XPoint { x: 0, y: 0 },
is_allowed,
_client_data: Box::from_raw(client_data),
};
// Set the spot location, if it's present.
if let Some(ic_spot) = ic_spot {
context.set_spot(xconn, ic_spot.x, ic_spot.y)
}
Ok(context)
}
unsafe fn create_none_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNone | ffi::XIMStatusNone,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
);
(!ic.is_null()).then(|| ic)
}
unsafe fn create_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
window: ffi::Window,
client_data: ffi::XPointer,
) -> Option<ffi::XIC> {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
);
if ic.is_null() {
None
} else {
Some(ic)
}
}
let preedit_callbacks = PreeditCallbacks::new(client_data);
let preedit_attr = util::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
&(preedit_callbacks.start_callback) as *const _,
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
&(preedit_callbacks.done_callback) as *const _,
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
&(preedit_callbacks.caret_callback) as *const _,
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
&(preedit_callbacks.draw_callback) as *const _,
ptr::null_mut::<()>(),
),
)
.expect("XVaCreateNestedList returned NULL");
unsafe fn create_ic_with_spot(
xconn: &Arc<XConnection>,
im: ffi::XIM,
window: ffi::Window,
ic_spot: ffi::XPoint,
) -> Option<ffi::XIC> {
let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot);
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
pre_edit_attr.ptr,
ptr::null_mut::<()>(),
);
if ic.is_null() {
None
} else {
Some(ic)
}
let ic = {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
preedit_attr.ptr,
ptr::null_mut::<()>(),
);
// If we've failed to create IC with preedit callbacks fallback to normal one.
if ic.is_null() {
(xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
)
} else {
ic
}
};
(!ic.is_null()).then(|| ic)
}
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
@@ -120,18 +326,34 @@ impl ImeContext {
xconn.check_errors()
}
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
// window and couldn't be changed.
//
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
if self.ic_spot.x == x && self.ic_spot.y == y {
if !self.is_allowed || self.ic_spot.x == x && self.ic_spot.y == y {
return;
}
self.ic_spot = ffi::XPoint { x, y };
unsafe {
let pre_edit_attr = create_pre_edit_attr(xconn, &self.ic_spot);
let preedit_attr = util::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,
ffi::XNSpotLocation_0.as_ptr(),
&self.ic_spot,
ptr::null_mut::<()>(),
),
)
.expect("XVaCreateNestedList returned NULL");
(xconn.xlib.XSetICValues)(
self.ic,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
pre_edit_attr.ptr,
preedit_attr.ptr,
ptr::null_mut::<()>(),
);
}

View File

@@ -3,6 +3,7 @@ use std::{collections::HashMap, mem, ptr, sync::Arc};
use super::{ffi, XConnection, XError};
use super::{context::ImeContext, input_method::PotentialInputMethods};
use crate::platform_impl::platform::x11::ime::ImeEventSender;
pub unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
(xconn.xlib.XCloseIM)(im);
@@ -22,6 +23,7 @@ pub struct ImeInner {
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
// WARNING: this is initially zeroed!
pub destroy_callback: ffi::XIMCallback,
pub event_sender: ImeEventSender,
// Indicates whether or not the the input method was destroyed on the server end
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
pub is_destroyed: bool,
@@ -29,13 +31,18 @@ pub struct ImeInner {
}
impl ImeInner {
pub fn new(xconn: Arc<XConnection>, potential_input_methods: PotentialInputMethods) -> Self {
pub fn new(
xconn: Arc<XConnection>,
potential_input_methods: PotentialInputMethods,
event_sender: ImeEventSender,
) -> Self {
ImeInner {
xconn,
im: ptr::null_mut(),
potential_input_methods,
contexts: HashMap::new(),
destroy_callback: unsafe { mem::zeroed() },
event_sender,
is_destroyed: false,
is_fallback: false,
}
@@ -58,10 +65,8 @@ impl ImeInner {
}
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
for context in self.contexts.values() {
if let &Some(ref context) = context {
self.destroy_ic_if_necessary(context.ic)?;
}
for context in self.contexts.values().flatten() {
self.destroy_ic_if_necessary(context.ic)?;
}
Ok(!self.is_destroyed)
}

View File

@@ -7,13 +7,12 @@ use std::{
sync::Arc,
};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use super::{ffi, util, XConnection, XError};
lazy_static! {
static ref GLOBAL_LOCK: Mutex<()> = Default::default();
}
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
let _lock = GLOBAL_LOCK.lock();
@@ -42,12 +41,12 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
#[derive(Debug)]
pub struct InputMethod {
pub im: ffi::XIM,
name: String,
_name: String,
}
impl InputMethod {
fn new(im: ffi::XIM, name: String) -> Self {
InputMethod { im, name }
InputMethod { im, _name: name }
}
}
@@ -63,11 +62,7 @@ pub enum InputMethodResult {
impl InputMethodResult {
pub fn is_fallback(&self) -> bool {
if let &InputMethodResult::Fallback(_) = self {
true
} else {
false
}
matches!(self, InputMethodResult::Fallback(_))
}
pub fn ok(self) -> Option<InputMethod> {
@@ -185,10 +180,10 @@ impl PotentialInputMethod {
}
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
// came from, and if it succceeded.
// came from, and if it succeeded.
#[derive(Debug, Clone)]
pub struct PotentialInputMethods {
// On correctly configured systems, the XMODIFIERS environemnt variable tells us everything we
// On correctly configured systems, the XMODIFIERS environment variable tells us everything we
// need to know.
xmodifiers: Option<PotentialInputMethod>,
// We have some standard options at our disposal that should ostensibly always work. For users
@@ -218,7 +213,7 @@ impl PotentialInputMethods {
// that case, we get `None` and end up skipping ahead to the next method.
xmodifiers,
fallbacks: [
// This is a standard input method that supports compose equences, which should
// This is a standard input method that supports compose sequences, which should
// always be available. `@im=none` appears to mean the same thing.
PotentialInputMethod::from_str("@im=local"),
// This explicitly specifies to use the implementation-dependent default, though
@@ -249,7 +244,7 @@ impl PotentialInputMethods {
pub fn open_im(
&mut self,
xconn: &Arc<XConnection>,
callback: Option<&dyn Fn() -> ()>,
callback: Option<&dyn Fn()>,
) -> InputMethodResult {
use self::InputMethodResult::*;
@@ -259,10 +254,8 @@ impl PotentialInputMethods {
let im = input_method.open_im(xconn);
if let Some(im) = im {
return XModifiers(im);
} else {
if let Some(ref callback) = callback {
callback();
}
} else if let Some(ref callback) = callback {
callback();
}
}

View File

@@ -19,9 +19,29 @@ use self::{
inner::{close_im, ImeInner},
input_method::PotentialInputMethods,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ImeEvent {
Enabled,
Start,
Update(String, usize),
End,
Disabled,
}
pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>;
pub type ImeSender = Sender<(ffi::Window, i16, i16)>;
pub type ImeReceiver = Receiver<ImeRequest>;
pub type ImeSender = Sender<ImeRequest>;
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
/// Request to control XIM handler from the window.
pub enum ImeRequest {
/// Set IME spot position for given `window_id`.
Position(ffi::Window, i16, i16),
/// Allow IME input for the given `window_id`.
Allow(ffi::Window, bool),
}
#[derive(Debug)]
pub enum ImeCreationError {
@@ -37,11 +57,14 @@ pub struct Ime {
}
impl Ime {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, ImeCreationError> {
pub fn new(
xconn: Arc<XConnection>,
event_sender: ImeEventSender,
) -> Result<Self, ImeCreationError> {
let potential_input_methods = PotentialInputMethods::new(&xconn);
let (mut inner, client_data) = {
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods));
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
let inner_ptr = Box::into_raw(inner);
let client_data = inner_ptr as _;
let destroy_callback = ffi::XIMCallback {
@@ -88,12 +111,54 @@ impl Ime {
// Ok(_) indicates that nothing went wrong internally
// Ok(true) indicates that the action was actually performed
// Ok(false) indicates that the action is not presently applicable
pub fn create_context(&mut self, window: ffi::Window) -> Result<bool, ImeContextCreationError> {
pub fn create_context(
&mut self,
window: ffi::Window,
with_preedit: bool,
) -> Result<bool, ImeContextCreationError> {
let context = if self.is_destroyed() {
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
None
} else {
Some(unsafe { ImeContext::new(&self.inner.xconn, self.inner.im, window, None) }?)
let context = unsafe {
ImeContext::new(
&self.inner.xconn,
self.inner.im,
window,
None,
with_preedit,
self.inner.event_sender.clone(),
)
.or_else(|_| {
debug!(
"failed to create an IME context {} preedit support",
if with_preedit { "with" } else { "without" }
);
ImeContext::new(
&self.inner.xconn,
self.inner.im,
window,
None,
!with_preedit,
self.inner.event_sender.clone(),
)
})
}?;
// Check the state on the context, since it could fail to enable or disable preedit.
let event = if context.is_allowed {
ImeEvent::Enabled
} else {
// There's no IME without preedit.
ImeEvent::Disabled
};
self.inner
.event_sender
.send((window, event))
.expect("Failed to send enabled event");
Some(context)
};
self.inner.contexts.insert(window, context);
Ok(!self.is_destroyed())
@@ -151,6 +216,24 @@ impl Ime {
context.set_spot(&self.xconn, x as _, y as _);
}
}
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
if self.is_destroyed() {
return;
}
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
if allowed == context.is_allowed {
return;
}
}
// Remove context for that window.
let _ = self.remove_context(window);
// Create new context supporting IME input.
let _ = self.create_context(window, allowed);
}
}
impl Drop for Ime {

View File

@@ -23,7 +23,7 @@ pub use self::{
};
use std::{
cell::RefCell,
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
ffi::CStr,
mem::{self, MaybeUninit},
@@ -32,7 +32,7 @@ use std::{
ptr,
rc::Rc,
slice,
sync::mpsc::{Receiver, Sender},
sync::mpsc::{Receiver, Sender, TryRecvError},
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
@@ -40,18 +40,24 @@ use std::{
use libc::{self, setlocale, LC_CTYPE};
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
util::modifiers::ModifierKeymap,
};
use crate::{
error::OsError as RootOsError,
event::{Event, StartCause},
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
event_loop::{
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
},
platform_impl::{
platform::{sticky_exit_callback, WindowId},
PlatformSpecificWindowBuilderAttributes,
},
window::WindowAttributes,
};
@@ -63,6 +69,40 @@ struct WakeSender<T> {
waker: Arc<Waker>,
}
struct PeekableReceiver<T> {
recv: Receiver<T>,
first: Option<T>,
}
impl<T> PeekableReceiver<T> {
pub fn from_recv(recv: Receiver<T>) -> Self {
Self { recv, first: None }
}
pub fn has_incoming(&mut self) -> bool {
if self.first.is_some() {
return true;
}
match self.recv.try_recv() {
Ok(v) => {
self.first = Some(v);
true
}
Err(TryRecvError::Empty) => false,
Err(TryRecvError::Disconnected) => {
warn!("Channel was disconnected when checking incoming");
false
}
}
}
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
if let Some(first) = self.first.take() {
return Ok(first);
}
self.recv.try_recv()
}
}
pub struct EventLoopWindowTarget<T> {
xconn: Arc<XConnection>,
wm_delete_window: ffi::Atom,
@@ -72,6 +112,7 @@ pub struct EventLoopWindowTarget<T> {
ime: RefCell<Ime>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
redraw_sender: WakeSender<WindowId>,
device_event_filter: Cell<DeviceEventFilter>,
_marker: ::std::marker::PhantomData<T>,
}
@@ -79,8 +120,8 @@ pub struct EventLoop<T: 'static> {
poll: Poll,
waker: Arc<Waker>,
event_processor: EventProcessor<T>,
redraw_channel: Receiver<WindowId>,
user_channel: Receiver<T>, //waker.wake needs to be called whenever something gets sent
redraw_receiver: PeekableReceiver<WindowId>,
user_receiver: PeekableReceiver<T>, //waker.wake needs to be called whenever something gets sent
user_sender: Sender<T>,
target: Rc<RootELW<T>>,
}
@@ -111,6 +152,7 @@ impl<T: 'static> EventLoop<T> {
.expect("Failed to call XInternAtoms when initializing drag and drop");
let (ime_sender, ime_receiver) = mpsc::channel();
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
// Input methods will open successfully without setting the locale, but it won't be
// possible to actually commit pre-edit sequences.
unsafe {
@@ -135,7 +177,7 @@ impl<T: 'static> EventLoop<T> {
}
}
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn));
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
panic!("Failed to open input method: {:#?}", state);
}
@@ -195,21 +237,27 @@ impl<T: 'static> EventLoop<T> {
let (user_sender, user_channel) = std::sync::mpsc::channel();
let (redraw_sender, redraw_channel) = std::sync::mpsc::channel();
let window_target = EventLoopWindowTarget {
ime,
root,
windows: Default::default(),
_marker: ::std::marker::PhantomData,
ime_sender,
xconn,
wm_delete_window,
net_wm_ping,
redraw_sender: WakeSender {
sender: redraw_sender, // not used again so no clone
waker: waker.clone(),
},
device_event_filter: Default::default(),
};
// Set initial device event filter.
window_target.update_device_event_filter(true);
let target = Rc::new(RootELW {
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
ime,
root,
windows: Default::default(),
_marker: ::std::marker::PhantomData,
ime_sender,
xconn,
wm_delete_window,
net_wm_ping,
redraw_sender: WakeSender {
sender: redraw_sender, // not used again so no clone
waker: waker.clone(),
},
}),
p: super::EventLoopWindowTarget::X(window_target),
_marker: ::std::marker::PhantomData,
});
@@ -219,12 +267,14 @@ impl<T: 'static> EventLoop<T> {
devices: Default::default(),
randr_event_offset,
ime_receiver,
ime_event_receiver,
xi2ext,
mod_keymap,
device_mod_state: Default::default(),
num_touch: 0,
first_touch: None,
active_window: None,
is_composing: false,
};
// Register for device hotplug events
@@ -240,8 +290,8 @@ impl<T: 'static> EventLoop<T> {
poll,
waker,
event_processor,
redraw_channel,
user_channel,
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
user_receiver: PeekableReceiver::from_recv(user_channel),
user_sender,
target,
}
@@ -258,33 +308,53 @@ impl<T: 'static> EventLoop<T> {
&self.target
}
pub fn run_return<F>(&mut self, mut callback: F)
pub fn run_return<F>(&mut self, mut callback: F) -> i32
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::default();
let mut events = Events::with_capacity(8);
let mut cause = StartCause::Init;
loop {
struct IterationResult {
deadline: Option<Instant>,
timeout: Option<Duration>,
wait_start: Instant,
}
fn single_iteration<T, F>(
this: &mut EventLoop<T>,
control_flow: &mut ControlFlow,
cause: &mut StartCause,
callback: &mut F,
) -> IterationResult
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
sticky_exit_callback(
crate::event::Event::NewEvents(cause),
&self.target,
&mut control_flow,
&mut callback,
crate::event::Event::NewEvents(*cause),
&this.target,
control_flow,
callback,
);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
// applications don't themselves have a formal suspend/resume lifecycle.
if *cause == StartCause::Init {
sticky_exit_callback(
crate::event::Event::Resumed,
&this.target,
control_flow,
callback,
);
}
// Process all pending events
self.drain_events(&mut callback, &mut control_flow);
this.drain_events(callback, control_flow);
// Empty the user event buffer
{
while let Ok(event) = self.user_channel.try_recv() {
while let Ok(event) = this.user_receiver.try_recv() {
sticky_exit_callback(
crate::event::Event::UserEvent(event),
&self.target,
&mut control_flow,
&mut callback,
&this.target,
control_flow,
callback,
);
}
}
@@ -292,26 +362,26 @@ impl<T: 'static> EventLoop<T> {
{
sticky_exit_callback(
crate::event::Event::MainEventsCleared,
&self.target,
&mut control_flow,
&mut callback,
&this.target,
control_flow,
callback,
);
}
// Empty the redraw requests
{
let mut windows = HashSet::new();
while let Ok(window_id) = self.redraw_channel.try_recv() {
while let Ok(window_id) = this.redraw_receiver.try_recv() {
windows.insert(window_id);
}
for window_id in windows {
let window_id = crate::window::WindowId(super::WindowId::X(window_id));
let window_id = crate::window::WindowId(window_id);
sticky_exit_callback(
Event::RedrawRequested(window_id),
&self.target,
&mut control_flow,
&mut callback,
&this.target,
control_flow,
callback,
);
}
}
@@ -319,9 +389,9 @@ impl<T: 'static> EventLoop<T> {
{
sticky_exit_callback(
crate::event::Event::RedrawEventsCleared,
&self.target,
&mut control_flow,
&mut callback,
&this.target,
control_flow,
callback,
);
}
@@ -329,14 +399,20 @@ impl<T: 'static> EventLoop<T> {
let (deadline, timeout);
match control_flow {
ControlFlow::Exit => break,
ControlFlow::ExitWithCode(_) => {
return IterationResult {
wait_start: start,
deadline: None,
timeout: None,
};
}
ControlFlow::Poll => {
cause = StartCause::Poll;
*cause = StartCause::Poll;
deadline = None;
timeout = Some(Duration::from_millis(0));
}
ControlFlow::Wait => {
cause = StartCause::WaitCancelled {
*cause = StartCause::WaitCancelled {
start,
requested_resume: None,
};
@@ -344,53 +420,88 @@ impl<T: 'static> EventLoop<T> {
timeout = None;
}
ControlFlow::WaitUntil(wait_deadline) => {
cause = StartCause::ResumeTimeReached {
*cause = StartCause::ResumeTimeReached {
start,
requested_resume: wait_deadline,
requested_resume: *wait_deadline,
};
timeout = if wait_deadline > start {
Some(wait_deadline - start)
timeout = if *wait_deadline > start {
Some(*wait_deadline - start)
} else {
Some(Duration::from_millis(0))
};
deadline = Some(wait_deadline);
deadline = Some(*wait_deadline);
}
}
// If the XConnection already contains buffered events, we don't
// need to wait for data on the socket.
if !self.event_processor.poll() {
if let Err(e) = self.poll.poll(&mut events, timeout) {
IterationResult {
wait_start: start,
deadline,
timeout,
}
}
let mut control_flow = ControlFlow::default();
let mut events = Events::with_capacity(8);
let mut cause = StartCause::Init;
// run the initial loop iteration
let mut iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback);
let exit_code = loop {
if let ControlFlow::ExitWithCode(code) = control_flow {
break code;
}
let has_pending = self.event_processor.poll()
|| self.user_receiver.has_incoming()
|| self.redraw_receiver.has_incoming();
if !has_pending {
// Wait until
if let Err(e) = self.poll.poll(&mut events, iter_result.timeout) {
if e.raw_os_error() != Some(libc::EINTR) {
panic!("epoll returned an error: {:?}", e);
}
}
events.clear();
if control_flow == ControlFlow::Wait {
// We don't go straight into executing the event loop iteration, we instead go
// to the start of this loop and check again if there's any pending event. We
// must do this because during the execution of the iteration we sometimes wake
// the mio waker, and if the waker is already awaken before we call poll(),
// then poll doesn't block, but it returns immediately. This caused the event
// loop to run continuously even if the control_flow was `Wait`
continue;
}
}
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
let wait_cancelled = iter_result
.deadline
.map_or(false, |deadline| Instant::now() < deadline);
if wait_cancelled {
cause = StartCause::WaitCancelled {
start,
requested_resume: deadline,
start: iter_result.wait_start,
requested_resume: iter_result.deadline,
};
}
}
iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback);
};
callback(
crate::event::Event::LoopDestroyed,
&self.target,
&mut control_flow,
);
exit_code
}
pub fn run<F>(mut self, callback: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
self.run_return(callback);
::std::process::exit(0);
let exit_code = self.run_return(callback);
::std::process::exit(exit_code);
}
fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow)
@@ -409,10 +520,7 @@ impl<T: 'static> EventLoop<T> {
target,
control_flow,
&mut |event, window_target, control_flow| {
if let Event::RedrawRequested(crate::window::WindowId(
super::WindowId::X(wid),
)) = event
{
if let Event::RedrawRequested(crate::window::WindowId(wid)) = event {
wt.redraw_sender.sender.send(wid).unwrap();
wt.redraw_sender.waker.wake().unwrap();
} else {
@@ -439,6 +547,37 @@ impl<T> EventLoopWindowTarget<T> {
pub fn x_connection(&self) -> &Arc<XConnection> {
&self.xconn
}
pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {
self.device_event_filter.set(filter);
}
/// Update the device event filter based on window focus.
pub fn update_device_event_filter(&self, focus: bool) {
let filter_events = self.device_event_filter.get() == DeviceEventFilter::Never
|| (self.device_event_filter.get() == DeviceEventFilter::Unfocused && !focus);
let mut mask = 0;
if !filter_events {
mask = ffi::XI_RawMotionMask
| ffi::XI_RawButtonPressMask
| ffi::XI_RawButtonReleaseMask
| ffi::XI_RawKeyPressMask
| ffi::XI_RawKeyReleaseMask;
}
self.xconn
.select_xinput_events(self.root, ffi::XIAllMasterDevices, mask)
.queue();
}
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
let mut display_handle = XlibDisplayHandle::empty();
display_handle.display = self.xconn.display as *mut _;
display_handle.screen =
unsafe { (self.xconn.xlib.XDefaultScreen)(self.xconn.display as *mut _) };
RawDisplayHandle::Xlib(display_handle)
}
}
impl<T: 'static> EventLoopProxy<T> {
@@ -490,15 +629,6 @@ impl<'a> Deref for DeviceInfo<'a> {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
impl WindowId {
pub const unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
@@ -519,7 +649,7 @@ impl Deref for Window {
}
impl Window {
pub fn new<T>(
pub(crate) fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
@@ -538,7 +668,7 @@ impl Drop for Window {
let window = self.deref();
let xconn = &window.xconn;
unsafe {
(xconn.xlib.XDestroyWindow)(xconn.display, window.id().0);
(xconn.xlib.XDestroyWindow)(xconn.display, window.id().0 as ffi::Window);
// If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about.
let _ = xconn.check_errors();
}
@@ -553,10 +683,7 @@ struct GenericEventCookie<'a> {
}
impl<'a> GenericEventCookie<'a> {
fn from_event<'b>(
xconn: &'b XConnection,
event: ffi::XEvent,
) -> Option<GenericEventCookie<'b>> {
fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'_>> {
unsafe {
let mut cookie: ffi::XGenericEventCookie = From::from(event);
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
@@ -584,7 +711,7 @@ struct XExtension {
}
fn mkwid(w: ffi::Window) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::WindowId::X(WindowId(w)))
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as u64))
}
fn mkdid(w: c_int) -> crate::event::DeviceId {
crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w)))
@@ -592,7 +719,7 @@ fn mkdid(w: c_int) -> crate::event::DeviceId {
#[derive(Debug)]
struct Device {
name: String,
_name: String,
scroll_axes: Vec<(i32, ScrollAxis)>,
// For master devices, this is the paired device (pointer <-> keyboard).
// For slave devices, this is the master.
@@ -613,52 +740,36 @@ enum ScrollOrientation {
}
impl Device {
fn new<T: 'static>(el: &EventProcessor<T>, info: &ffi::XIDeviceInfo) -> Self {
fn new(info: &ffi::XIDeviceInfo) -> Self {
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
let mut scroll_axes = Vec::new();
let wt = get_xtarget(&el.target);
if Device::physical_device(info) {
// Register for global raw events
let mask = ffi::XI_RawMotionMask
| ffi::XI_RawButtonPressMask
| ffi::XI_RawButtonReleaseMask
| ffi::XI_RawKeyPressMask
| ffi::XI_RawKeyReleaseMask;
// The request buffer is flushed when we poll for events
wt.xconn
.select_xinput_events(wt.root, info.deviceid, mask)
.queue();
// Identify scroll axes
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
match class._type {
ffi::XIScrollClass => {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
};
scroll_axes.push((
info.number,
ScrollAxis {
increment: info.increment,
orientation: match info.scroll_type {
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
_ => unreachable!(),
},
position: 0.0,
if class._type == ffi::XIScrollClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
};
scroll_axes.push((
info.number,
ScrollAxis {
increment: info.increment,
orientation: match info.scroll_type {
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
_ => unreachable!(),
},
));
}
_ => {}
position: 0.0,
},
));
}
}
}
let mut device = Device {
name: name.into_owned(),
_name: name.into_owned(),
scroll_axes,
attachment: info.attachment,
};
@@ -670,20 +781,17 @@ impl Device {
if Device::physical_device(info) {
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
match class._type {
ffi::XIValuatorClass => {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
};
if let Some(&mut (_, ref mut axis)) = self
.scroll_axes
.iter_mut()
.find(|&&mut (axis, _)| axis == info.number)
{
axis.position = info.value;
}
if class._type == ffi::XIValuatorClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
};
if let Some(&mut (_, ref mut axis)) = self
.scroll_axes
.iter_mut()
.find(|&&mut (axis, _)| axis == info.number)
{
axis.position = info.value;
}
_ => {}
}
}
}

View File

@@ -1,11 +1,13 @@
use std::os::raw::*;
use std::slice;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use super::{
ffi::{
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources,
},
util, XConnection, XError,
};
@@ -18,9 +20,7 @@ use crate::{
// Used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false;
lazy_static! {
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
}
static MONITORS: Lazy<Mutex<Option<Vec<MonitorHandle>>>> = Lazy::new(Mutex::default);
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
// We update this lazily.
@@ -31,7 +31,7 @@ pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) native_mode: RRMode,
pub(crate) monitor: Option<MonitorHandle>,
}
@@ -48,8 +48,8 @@ impl VideoMode {
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
pub fn refresh_rate_millihertz(&self) -> u32 {
self.refresh_rate_millihertz
}
#[inline]
@@ -72,6 +72,8 @@ pub struct MonitorHandle {
position: (i32, i32),
/// If the monitor is the primary one
primary: bool,
/// The refresh rate used by monitor.
refresh_rate_millihertz: Option<u32>,
/// The DPI scale factor
pub(crate) scale_factor: f64,
/// Used to determine which windows are on this monitor
@@ -90,7 +92,7 @@ impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
Some(self.cmp(other))
}
}
@@ -106,6 +108,15 @@ impl std::hash::Hash for MonitorHandle {
}
}
#[inline]
pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> {
if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 {
Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32)
} else {
None
}
}
impl MonitorHandle {
fn new(
xconn: &XConnection,
@@ -117,10 +128,22 @@ impl MonitorHandle {
let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
// Get the refresh rate of the current video mode.
let current_mode = unsafe { (*crtc).mode };
let screen_modes =
unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) };
let refresh_rate_millihertz = screen_modes
.iter()
.find(|mode| mode.id == current_mode)
.and_then(mode_refresh_rate_millihertz);
let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle {
id,
name,
refresh_rate_millihertz,
scale_factor,
dimensions,
position,
@@ -137,6 +160,7 @@ impl MonitorHandle {
scale_factor: 1.0,
dimensions: (1, 1),
position: (0, 0),
refresh_rate_millihertz: None,
primary: true,
rect: util::AaRect::new((0, 0), (1, 1)),
video_modes: Vec::new(),
@@ -165,6 +189,10 @@ impl MonitorHandle {
self.position.into()
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.refresh_rate_millihertz
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.scale_factor
@@ -204,7 +232,7 @@ impl XConnection {
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
if overlapping_area > largest_overlap {
largest_overlap = overlapping_area;
matched_monitor = &monitor;
matched_monitor = monitor;
}
}
@@ -230,11 +258,11 @@ impl XConnection {
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
}
let mut available;
let mut has_primary = false;
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
let mut available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
@@ -242,8 +270,11 @@ impl XConnection {
if is_active {
let is_primary = *(*crtc).outputs.offset(0) == primary;
has_primary |= is_primary;
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
.map(|monitor_id| available.push(monitor_id));
if let Some(monitor_id) =
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
{
available.push(monitor_id)
}
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
}

View File

@@ -5,15 +5,14 @@ use std::{
os::raw::*,
};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use super::*;
type AtomCache = HashMap<CString, ffi::Atom>;
lazy_static! {
static ref ATOM_CACHE: Mutex<AtomCache> = Mutex::new(HashMap::with_capacity(2048));
}
static ATOM_CACHE: Lazy<Mutex<AtomCache>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(2048)));
impl XConnection {
pub fn get_atom<T: AsRef<CStr> + Debug>(&self, name: T) -> ffi::Atom {

View File

@@ -23,9 +23,9 @@ impl Format {
pub fn get_actual_size(&self) -> usize {
match self {
&Format::Char => mem::size_of::<c_char>(),
&Format::Short => mem::size_of::<c_short>(),
&Format::Long => mem::size_of::<c_long>(),
Format::Char => mem::size_of::<c_char>(),
Format::Short => mem::size_of::<c_short>(),
Format::Long => mem::size_of::<c_long>(),
}
}
}

View File

@@ -98,7 +98,7 @@ pub struct LogicalFrameExtents {
pub bottom: f64,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameExtentsHeuristicPath {
Supported,
UnsupportedNested,
@@ -248,7 +248,7 @@ impl XConnection {
);
// The list of children isn't used
if children != ptr::null_mut() {
if !children.is_null() {
(self.xlib.XFree)(children as *mut _);
}
@@ -370,8 +370,12 @@ impl XConnection {
let top = offset_y;
let bottom = diff_y.saturating_sub(offset_y);
let frame_extents =
FrameExtents::new(left.into(), right.into(), top.into(), bottom.into());
let frame_extents = FrameExtents::new(
left as c_ulong,
right as c_ulong,
top as c_ulong,
bottom as c_ulong,
);
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedNested,
@@ -379,7 +383,7 @@ impl XConnection {
} else {
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
// border value. This is convenient, since we can use it to get an accurate frame.
let frame_extents = FrameExtents::from_border(border.into());
let frame_extents = FrameExtents::from_border(border as c_ulong);
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedBordered,

View File

@@ -173,6 +173,12 @@ impl MotifHints {
}
}
impl Default for MotifHints {
fn default() -> Self {
Self::new()
}
}
impl MwmHints {
fn as_slice(&self) -> &[c_ulong] {
unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) }
@@ -317,7 +323,7 @@ impl XConnection {
let mut hints = MotifHints::new();
if let Ok(props) = self.get_property::<c_ulong>(window, motif_hints, motif_hints) {
hints.hints.flags = props.get(0).cloned().unwrap_or(0);
hints.hints.flags = props.first().cloned().unwrap_or(0);
hints.hints.functions = props.get(1).cloned().unwrap_or(0);
hints.hints.decorations = props.get(2).cloned().unwrap_or(0);
hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long;

View File

@@ -1,3 +1,5 @@
#![allow(clippy::assertions_on_constants)]
use super::*;
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
@@ -27,7 +29,7 @@ impl Icon {
data.push(rgba_icon.height as Cardinal);
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
let pixel = unsafe { &*pixels.add(pixel_index) };
data.push(pixel.to_packed_argb());
}
data

View File

@@ -36,7 +36,7 @@ impl Iterator for KeymapIter<'_> {
fn next(&mut self) -> Option<ffi::KeyCode> {
if self.item.is_none() {
while let Some((index, &item)) = self.iter.next() {
for (index, &item) in self.iter.by_ref() {
if item != 0 {
self.index = index;
self.item = Some(item);

View File

@@ -151,7 +151,7 @@ impl ModifierKeyState {
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
if let Some(modifier) = self.keys.remove(&keycode) {
if self.keys.values().find(|&&m| m == modifier).is_none() {
if !self.keys.values().any(|&m| m == modifier) {
set_modifier(&mut self.state, modifier, false);
}
}

View File

@@ -4,6 +4,7 @@ use super::{
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use crate::platform_impl::platform::x11::monitor;
use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode};
/// Represents values of `WINIT_HIDPI_FACTOR`.
@@ -39,15 +40,15 @@ impl XConnection {
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
(self.xlib.XrmInitialize)();
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
if resource_manager_str == ptr::null_mut() {
if resource_manager_str.is_null() {
return None;
}
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
let name: &str = "Xft.dpi:\t";
for pair in res.split("\n") {
for pair in res.split('\n') {
if pair.starts_with(&name) {
let res = &pair[name.len()..];
return f64::from_str(&res).ok();
return f64::from_str(res).ok();
}
}
}
@@ -80,18 +81,13 @@ impl XConnection {
// XRROutputInfo contains an array of mode ids that correspond to
// modes in the array in XRRScreenResources
.filter(|x| output_modes.iter().any(|id| x.id == *id))
.map(|x| {
let refresh_rate = if x.dotClock > 0 && x.hTotal > 0 && x.vTotal > 0 {
x.dotClock as u64 * 1000 / (x.hTotal as u64 * x.vTotal as u64)
} else {
0
};
.map(|mode| {
VideoMode {
size: (x.width, x.height),
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
size: (mode.width, mode.height),
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode)
.unwrap_or(0),
bit_depth: bit_depth as u16,
native_mode: x.id,
native_mode: mode.id,
// This is populated in `MonitorHandle::video_modes` as the
// video mode is returned to the user
monitor: None,
@@ -164,7 +160,9 @@ impl XConnection {
(self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, scale_factor, modes))
}
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
#[must_use]
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> {
unsafe {
let mut major = 0;
let mut minor = 0;
@@ -195,12 +193,13 @@ impl XConnection {
(self.xrandr.XRRFreeScreenResources)(resources);
if status == Success as i32 {
Ok(())
Some(())
} else {
Err(())
None
}
}
}
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
unsafe {
let mut major = 0;

View File

@@ -58,7 +58,7 @@ impl XConnection {
property,
// This offset is in terms of 32-bit chunks.
offset,
// This is the quanity of 32-bit chunks to receive at once.
// This is the quantity of 32-bit chunks to receive at once.
PROPERTY_BUFFER_SIZE,
ffi::False,
property_type,
@@ -97,7 +97,7 @@ impl XConnection {
quantity_returned,
new_data,
);*/
data.extend_from_slice(&new_data);
data.extend_from_slice(new_data);
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
(self.xlib.XFree)(buf as _); // Don't try to access new_data after this.
} else {

View File

@@ -1,12 +1,12 @@
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use super::*;
// This info is global to the window manager.
lazy_static! {
static ref SUPPORTED_HINTS: Mutex<Vec<ffi::Atom>> = Mutex::new(Vec::with_capacity(0));
static ref WM_NAME: Mutex<Option<String>> = Mutex::new(None);
}
static SUPPORTED_HINTS: Lazy<Mutex<Vec<ffi::Atom>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(0)));
static WM_NAME: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
pub fn hint_is_supported(hint: ffi::Atom) -> bool {
(*SUPPORTED_HINTS.lock()).contains(&hint)
@@ -60,13 +60,9 @@ impl XConnection {
let root_window_wm_check = {
let result = self.get_property(root, check_atom, ffi::XA_WINDOW);
let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned());
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
wm_check?
};
// Querying the same property on the child window we were given, we should get this child
@@ -74,13 +70,9 @@ impl XConnection {
let child_window_wm_check = {
let result = self.get_property(root_window_wm_check, check_atom, ffi::XA_WINDOW);
let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned());
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
wm_check?
};
// These values should be the same.

View File

@@ -1,4 +1,3 @@
use raw_window_handle::XlibHandle;
use std::{
cmp, env,
ffi::CString,
@@ -8,10 +7,11 @@ use std::{
ptr, slice,
sync::Arc,
};
use x11_dl::xlib::TrueColor;
use libc;
use parking_lot::Mutex;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle};
use x11_dl::xlib::TrueColor;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
@@ -22,11 +22,12 @@ use crate::{
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode,
},
window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
window::{CursorGrabMode, CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
};
use super::{
ffi, util, EventLoopWindowTarget, ImeSender, WakeSender, WindowId, XConnection, XError,
ffi, util, EventLoopWindowTarget, ImeRequest, ImeSender, WakeSender, WindowId, XConnection,
XError,
};
#[derive(Debug)]
@@ -36,6 +37,8 @@ pub struct SharedState {
pub position: Option<(i32, i32)>,
pub inner_position: Option<(i32, i32)>,
pub inner_position_rel_parent: Option<(i32, i32)>,
pub is_resizable: bool,
pub is_decorated: bool,
pub last_monitor: X11MonitorHandle,
pub dpi_adjusted: Option<(u32, u32)>,
pub fullscreen: Option<Fullscreen>,
@@ -62,8 +65,8 @@ pub enum Visibility {
}
impl SharedState {
fn new(last_monitor: X11MonitorHandle, is_visible: bool) -> Mutex<Self> {
let visibility = if is_visible {
fn new(last_monitor: X11MonitorHandle, window_attributes: &WindowAttributes) -> Mutex<Self> {
let visibility = if window_attributes.visible {
Visibility::YesWait
} else {
Visibility::No
@@ -73,6 +76,8 @@ impl SharedState {
last_monitor,
visibility,
is_resizable: window_attributes.resizable,
is_decorated: window_attributes.decorations,
cursor_pos: None,
size: None,
position: None,
@@ -101,7 +106,7 @@ pub struct UnownedWindow {
root: ffi::Window, // never changes
screen_id: i32, // never changes
cursor: Mutex<CursorIcon>,
cursor_grabbed: Mutex<bool>,
cursor_grabbed_mode: Mutex<CursorGrabMode>,
cursor_visible: Mutex<bool>,
ime_sender: Mutex<ImeSender>,
pub shared_state: Mutex<SharedState>,
@@ -109,7 +114,7 @@ pub struct UnownedWindow {
}
impl UnownedWindow {
pub fn new<T>(
pub(crate) fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
window_attrs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
@@ -150,7 +155,7 @@ impl UnownedWindow {
let position = window_attrs
.position
.map(|position| position.to_physical::<i32>(scale_factor).into());
.map(|position| position.to_physical::<i32>(scale_factor));
let dimensions = {
// x11 only applies constraints when the window is actively resized
@@ -184,7 +189,7 @@ impl UnownedWindow {
// creating
let (visual, depth, require_colormap) = match pl_attribs.visual_infos {
Some(vi) => (vi.visual, vi.depth, false),
None if window_attrs.transparent == true => {
None if window_attrs.transparent => {
// Find a suitable visual
let mut vinfo = MaybeUninit::uninit();
let vinfo_initialized = unsafe {
@@ -271,10 +276,10 @@ impl UnownedWindow {
root,
screen_id,
cursor: Default::default(),
cursor_grabbed: Mutex::new(false),
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
shared_state: SharedState::new(guessed_monitor, window_attrs.visible),
shared_state: SharedState::new(guessed_monitor, &window_attrs),
redraw_sender: WakeSender {
waker: event_loop.redraw_sender.waker.clone(),
sender: event_loop.redraw_sender.sender.clone(),
@@ -306,11 +311,11 @@ impl UnownedWindow {
// WM_CLASS must be set *before* mapping the window, as per ICCCM!
{
let (class, instance) = if let Some((instance, class)) = pl_attribs.class {
let instance = CString::new(instance.as_str())
let (class, instance) = if let Some(name) = pl_attribs.name {
let instance = CString::new(name.instance.as_str())
.expect("`WM_CLASS` instance contained null byte");
let class =
CString::new(class.as_str()).expect("`WM_CLASS` class contained null byte");
let class = CString::new(name.general.as_str())
.expect("`WM_CLASS` class contained null byte");
(instance, class)
} else {
let class = env::args()
@@ -341,7 +346,9 @@ impl UnownedWindow {
} //.queue();
}
window.set_pid().map(|flusher| flusher.queue());
if let Some(flusher) = window.set_pid() {
flusher.queue()
}
window.set_window_types(pl_attribs.x11_window_types).queue();
@@ -430,8 +437,7 @@ impl UnownedWindow {
}
// Select XInput2 events
let mask = {
let mask = ffi::XI_MotionMask
let mask = ffi::XI_MotionMask
| ffi::XI_ButtonPressMask
| ffi::XI_ButtonReleaseMask
//| ffi::XI_KeyPressMask
@@ -443,14 +449,15 @@ impl UnownedWindow {
| ffi::XI_TouchBeginMask
| ffi::XI_TouchUpdateMask
| ffi::XI_TouchEndMask;
mask
};
xconn
.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask)
.queue();
{
let result = event_loop.ime.borrow_mut().create_context(window.xwindow);
let result = event_loop
.ime
.borrow_mut()
.create_context(window.xwindow, false);
if let Err(err) = result {
let e = match err {
ImeContextCreationError::XError(err) => OsError::XError(err),
@@ -467,9 +474,10 @@ impl UnownedWindow {
window.set_maximized_inner(window_attrs.maximized).queue();
}
if window_attrs.fullscreen.is_some() {
window
.set_fullscreen_inner(window_attrs.fullscreen.clone())
.map(|flusher| flusher.queue());
if let Some(flusher) = window.set_fullscreen_inner(window_attrs.fullscreen.clone())
{
flusher.queue()
}
if let Some(PhysicalPosition { x, y }) = position {
let shared_state = window.shared_state.get_mut();
@@ -880,6 +888,7 @@ impl UnownedWindow {
}
fn set_decorations_inner(&self, decorations: bool) -> util::Flusher<'_> {
self.shared_state.lock().is_decorated = decorations;
let mut hints = self.xconn.get_motif_hints(self.xwindow);
hints.set_decorations(decorations);
@@ -895,6 +904,11 @@ impl UnownedWindow {
self.invalidate_cached_frame_extents();
}
#[inline]
pub fn is_decorated(&self) -> bool {
self.shared_state.lock().is_decorated
}
fn set_maximizable_inner(&self, maximizable: bool) -> util::Flusher<'_> {
let mut hints = self.xconn.get_motif_hints(self.xwindow);
@@ -979,6 +993,11 @@ impl UnownedWindow {
}
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
Some(self.shared_state.lock().visibility == Visibility::Yes)
}
fn update_cached_frame_extents(&self) {
let extents = self
.xconn
@@ -1100,8 +1119,15 @@ impl UnownedWindow {
#[inline]
pub fn set_inner_size(&self, size: Size) {
let scale_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(scale_factor).into();
self.set_inner_size_physical(width, height);
let size = size.to_physical::<u32>(scale_factor).into();
if !self.shared_state.lock().is_resizable {
self.update_normal_hints(|normal_hints| {
normal_hints.set_min_size(Some(size));
normal_hints.set_max_size(Some(size));
})
.expect("Failed to call `XSetWMNormalHints`");
}
self.set_inner_size_physical(size.0, size.1);
}
fn update_normal_hints<F>(&self, callback: F) -> Result<(), XError>
@@ -1189,6 +1215,7 @@ impl UnownedWindow {
let window_size = Some(Size::from(self.inner_size()));
(window_size, window_size)
};
self.shared_state.lock().is_resizable = resizable;
self.set_maximizable_inner(resizable).queue();
@@ -1206,6 +1233,11 @@ impl UnownedWindow {
.expect("Failed to call `XSetWMNormalHints`");
}
#[inline]
pub fn is_resizable(&self) -> bool {
self.shared_state.lock().is_resizable
}
#[inline]
pub fn xlib_display(&self) -> *mut c_void {
self.xconn.display as _
@@ -1240,64 +1272,75 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed.lock();
if grab == *grabbed_lock {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed_mode.lock();
if mode == *grabbed_lock {
return Ok(());
}
unsafe {
// We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`.
// Therefore, this is common to both codepaths.
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
let result = if grab {
let result = unsafe {
(self.xconn.xlib.XGrabPointer)(
self.xconn.display,
self.xwindow,
ffi::True,
(ffi::ButtonPressMask
| ffi::ButtonReleaseMask
| ffi::EnterWindowMask
| ffi::LeaveWindowMask
| ffi::PointerMotionMask
| ffi::PointerMotionHintMask
| ffi::Button1MotionMask
| ffi::Button2MotionMask
| ffi::Button3MotionMask
| ffi::Button4MotionMask
| ffi::Button5MotionMask
| ffi::ButtonMotionMask
| ffi::KeymapStateMask) as c_uint,
ffi::GrabModeAsync,
ffi::GrabModeAsync,
self.xwindow,
0,
ffi::CurrentTime,
)
};
match result {
ffi::GrabSuccess => Ok(()),
ffi::AlreadyGrabbed => {
Err("Cursor could not be grabbed: already grabbed by another client")
}
ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"),
ffi::GrabNotViewable => {
Err("Cursor could not be grabbed: grab location not viewable")
}
ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"),
_ => unreachable!(),
}
.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err))))
} else {
self.xconn
let result = match mode {
CursorGrabMode::None => self
.xconn
.flush_requests()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))),
CursorGrabMode::Confined => {
let result = unsafe {
(self.xconn.xlib.XGrabPointer)(
self.xconn.display,
self.xwindow,
ffi::True,
(ffi::ButtonPressMask
| ffi::ButtonReleaseMask
| ffi::EnterWindowMask
| ffi::LeaveWindowMask
| ffi::PointerMotionMask
| ffi::PointerMotionHintMask
| ffi::Button1MotionMask
| ffi::Button2MotionMask
| ffi::Button3MotionMask
| ffi::Button4MotionMask
| ffi::Button5MotionMask
| ffi::ButtonMotionMask
| ffi::KeymapStateMask) as c_uint,
ffi::GrabModeAsync,
ffi::GrabModeAsync,
self.xwindow,
0,
ffi::CurrentTime,
)
};
match result {
ffi::GrabSuccess => Ok(()),
ffi::AlreadyGrabbed => {
Err("Cursor could not be confined: already confined by another client")
}
ffi::GrabInvalidTime => Err("Cursor could not be confined: invalid time"),
ffi::GrabNotViewable => {
Err("Cursor could not be confined: confine location not viewable")
}
ffi::GrabFrozen => {
Err("Cursor could not be confined: frozen by another client")
}
_ => unreachable!(),
}
.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err))))
}
CursorGrabMode::Locked => {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
};
if result.is_ok() {
*grabbed_lock = grab;
*grabbed_lock = mode;
}
result
}
@@ -1337,6 +1380,11 @@ impl UnownedWindow {
self.set_cursor_position_physical(x, y)
}
#[inline]
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
let pointer = self
.xconn
@@ -1349,14 +1397,14 @@ impl UnownedWindow {
// we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
// if the cursor isn't currently grabbed
let mut grabbed_lock = self.cursor_grabbed.lock();
let mut grabbed_lock = self.cursor_grabbed_mode.lock();
unsafe {
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
self.xconn
.flush_requests()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
*grabbed_lock = false;
*grabbed_lock = CursorGrabMode::None;
// we keep the lock until we are done
self.xconn
@@ -1377,17 +1425,21 @@ impl UnownedWindow {
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
}
pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
let _ = self
.ime_sender
.lock()
.send((self.xwindow, x as i16, y as i16));
}
#[inline]
pub fn set_ime_position(&self, spot: Position) {
let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
self.set_ime_position_physical(x, y);
let _ = self
.ime_sender
.lock()
.send(ImeRequest::Position(self.xwindow, x, y));
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
let _ = self
.ime_sender
.lock()
.send(ImeRequest::Allow(self.xwindow, allowed));
}
#[inline]
@@ -1444,23 +1496,30 @@ impl UnownedWindow {
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.xwindow)
WindowId(self.xwindow as u64)
}
#[inline]
pub fn request_redraw(&self) {
self.redraw_sender
.sender
.send(WindowId(self.xwindow))
.send(WindowId(self.xwindow as u64))
.unwrap();
self.redraw_sender.waker.wake().unwrap();
}
#[inline]
pub fn raw_window_handle(&self) -> XlibHandle {
let mut handle = XlibHandle::empty();
handle.window = self.xlib_window();
handle.display = self.xlib_display();
handle
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = XlibWindowHandle::empty();
window_handle.window = self.xlib_window();
RawWindowHandle::Xlib(window_handle)
}
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = XlibDisplayHandle::empty();
display_handle.display = self.xlib_display();
display_handle.screen = self.screen_id;
RawDisplayHandle::Xlib(display_handle)
}
}

View File

@@ -8,6 +8,7 @@ use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel},
};
use once_cell::sync::Lazy;
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event};
@@ -16,19 +17,17 @@ pub struct AppClass(pub *const Class);
unsafe impl Send for AppClass {}
unsafe impl Sync for AppClass {}
lazy_static! {
pub static ref APP_CLASS: AppClass = unsafe {
let superclass = class!(NSApplication);
let mut decl = ClassDecl::new("WinitApp", superclass).unwrap();
pub static APP_CLASS: Lazy<AppClass> = Lazy::new(|| unsafe {
let superclass = class!(NSApplication);
let mut decl = ClassDecl::new("WinitApp", superclass).unwrap();
decl.add_method(
sel!(sendEvent:),
send_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(sendEvent:),
send_event as extern "C" fn(&Object, Sel, id),
);
AppClass(decl.register())
};
}
AppClass(decl.register())
});
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)

View File

@@ -1,47 +1,48 @@
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
use std::{
cell::{RefCell, RefMut},
os::raw::c_void,
};
use cocoa::base::id;
use objc::{
declare::ClassDecl,
runtime::{Class, Object, Sel},
};
use std::{
cell::{RefCell, RefMut},
os::raw::c_void,
};
use once_cell::sync::Lazy;
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
static AUX_DELEGATE_STATE_NAME: &str = "auxState";
pub struct AuxDelegateState {
/// We store this value in order to be able to defer setting the activation policy until
/// after the app has finished launching. If the activation policy is set earlier, the
/// menubar is initially unresponsive on macOS 10.15 for example.
pub activation_policy: ActivationPolicy,
pub create_default_menu: bool,
pub default_menu: bool,
}
pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
unsafe impl Sync for AppDelegateClass {}
lazy_static! {
pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
let superclass = class!(NSResponder);
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
pub static APP_DELEGATE_CLASS: Lazy<AppDelegateClass> = Lazy::new(|| unsafe {
let superclass = class!(NSResponder);
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillTerminate:),
will_terminate as extern "C" fn(&Object, Sel, id),
);
AppDelegateClass(decl.register())
};
}
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
AppDelegateClass(decl.register())
});
/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS
pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> {
@@ -54,11 +55,12 @@ extern "C" fn new(class: &Class, _: Sel) -> id {
unsafe {
let this: id = msg_send![class, alloc];
let this: id = msg_send![this, init];
// TODO: Remove the need for this initialization here
(*this).set_ivar(
AUX_DELEGATE_STATE_NAME,
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
activation_policy: ActivationPolicy::Regular,
create_default_menu: true,
default_menu: true,
}))) as *mut c_void,
);
this
@@ -70,12 +72,18 @@ extern "C" fn dealloc(this: &Object, _: Sel) {
let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME));
// As soon as the box is constructed it is immediately dropped, releasing the underlying
// memory
Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>);
drop(Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>));
}
}
extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationDidFinishLaunching`");
trace_scope!("applicationDidFinishLaunching:");
AppState::launched(this);
trace!("Completed `applicationDidFinishLaunching`");
}
extern "C" fn will_terminate(_this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationWillTerminate`");
// TODO: Notify every window that it will be destroyed, like done in iOS?
AppState::exit();
trace!("Completed `applicationWillTerminate`");
}

View File

@@ -19,8 +19,9 @@ use cocoa::{
};
use objc::{
rc::autoreleasepool,
runtime::{Object, YES},
runtime::{Object, BOOL, NO, YES},
};
use once_cell::sync::Lazy;
use crate::{
dpi::LogicalSize,
@@ -41,9 +42,7 @@ use crate::{
window::WindowId,
};
lazy_static! {
static ref HANDLER: Handler = Default::default();
}
static HANDLER: Lazy<Handler> = Lazy::new(Default::default);
impl<'a, Never> Event<'a, Never> {
fn userify<T: 'static>(self) -> Event<'a, T> {
@@ -60,9 +59,11 @@ pub trait EventHandler: Debug {
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
pub(crate) type Callback<T> =
RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>;
struct EventLoopHandler<T: 'static> {
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
will_exit: bool,
callback: Weak<Callback<T>>,
window_target: Rc<RootWindowTarget<T>>,
}
@@ -98,25 +99,25 @@ impl<T> Debug for EventLoopHandler<T> {
impl<T> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
self.with_callback(|this, mut callback| {
(callback)(event.userify(), &this.window_target, control_flow);
this.will_exit |= *control_flow == ControlFlow::Exit;
if this.will_exit {
*control_flow = ControlFlow::Exit;
if let ControlFlow::ExitWithCode(code) = *control_flow {
let dummy = &mut ControlFlow::ExitWithCode(code);
(callback)(event.userify(), &this.window_target, dummy);
} else {
(callback)(event.userify(), &this.window_target, control_flow);
}
});
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
self.with_callback(|this, mut callback| {
let mut will_exit = this.will_exit;
for event in this.window_target.p.receiver.try_iter() {
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
will_exit |= *control_flow == ControlFlow::Exit;
if will_exit {
*control_flow = ControlFlow::Exit;
if let ControlFlow::ExitWithCode(code) = *control_flow {
let dummy = &mut ControlFlow::ExitWithCode(code);
(callback)(Event::UserEvent(event), &this.window_target, dummy);
} else {
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
}
}
this.will_exit = will_exit;
});
}
}
@@ -125,7 +126,6 @@ impl<T> EventHandler for EventLoopHandler<T> {
struct Handler {
ready: AtomicBool,
in_callback: AtomicBool,
dialog_is_closing: AtomicBool,
control_flow: Mutex<ControlFlow>,
control_flow_prev: Mutex<ControlFlow>,
start_time: Mutex<Option<Instant>>,
@@ -143,7 +143,7 @@ impl Handler {
self.pending_events.lock().unwrap()
}
fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
fn redraw(&self) -> MutexGuard<'_, Vec<WindowId>> {
self.pending_redraw.lock().unwrap()
}
@@ -160,7 +160,10 @@ impl Handler {
}
fn should_exit(&self) -> bool {
*self.control_flow.lock().unwrap() == ControlFlow::Exit
matches!(
*self.control_flow.lock().unwrap(),
ControlFlow::ExitWithCode(_)
)
}
fn get_control_flow_and_update_prev(&self) -> ControlFlow {
@@ -184,11 +187,11 @@ impl Handler {
}
fn take_events(&self) -> VecDeque<EventWrapper> {
mem::replace(&mut *self.events(), Default::default())
mem::take(&mut *self.events())
}
fn should_redraw(&self) -> Vec<WindowId> {
mem::replace(&mut *self.redraw(), Default::default())
mem::take(&mut *self.redraw())
}
fn get_in_callback(&self) -> bool {
@@ -257,27 +260,26 @@ impl Handler {
}
}
pub static INTERRUPT_EVENT_LOOP_EXIT: AtomicBool = AtomicBool::new(false);
pub enum AppState {}
impl AppState {
pub fn set_callback<T>(
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
window_target: Rc<RootWindowTarget<T>>,
) {
pub fn set_callback<T>(callback: Weak<Callback<T>>, window_target: Rc<RootWindowTarget<T>>) {
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback,
will_exit: false,
window_target,
}));
}
pub fn exit() {
pub fn exit() -> i32 {
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed));
HANDLER.set_in_callback(false);
HANDLER.callback.lock().unwrap().take();
if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 {
code
} else {
0
}
}
pub fn launched(app_delegate: &Object) {
@@ -290,7 +292,7 @@ impl AppState {
};
HANDLER.set_ready();
HANDLER.waker().start();
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).create_default_menu };
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).default_menu };
if create_default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
@@ -300,6 +302,9 @@ impl AppState {
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)));
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
// applications don't themselves have a formal suspend/resume lifecycle.
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed));
HANDLER.set_in_callback(false);
}
@@ -332,7 +337,7 @@ impl AppState {
}
}
}
ControlFlow::Exit => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
};
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause)));
@@ -356,14 +361,16 @@ impl AppState {
}
pub fn queue_event(wrapper: EventWrapper) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
let is_main_thread: BOOL = unsafe { msg_send!(class!(NSThread), isMainThread) };
if is_main_thread == NO {
panic!("Event queued from different thread: {:#?}", wrapper);
}
HANDLER.events().push_back(wrapper);
}
pub fn queue_events(mut wrappers: VecDeque<EventWrapper>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
let is_main_thread: BOOL = unsafe { msg_send!(class!(NSThread), isMainThread) };
if is_main_thread == NO {
panic!("Events queued from different thread: {:#?}", wrappers);
}
HANDLER.events().append(&mut wrappers);
@@ -395,44 +402,17 @@ impl AppState {
if HANDLER.should_exit() {
unsafe {
let app: id = NSApp();
let windows: id = msg_send![app, windows];
let window_count: usize = msg_send![windows, count];
let dialog_open = if window_count > 1 {
let dialog: id = msg_send![windows, lastObject];
let is_main_window: bool = msg_send![dialog, isMainWindow];
msg_send![dialog, isVisible] && !is_main_window
} else {
false
};
let dialog_is_closing = HANDLER.dialog_is_closing.load(Ordering::SeqCst);
autoreleasepool(|| {
if !INTERRUPT_EVENT_LOOP_EXIT.load(Ordering::SeqCst)
&& !dialog_open
&& !dialog_is_closing
{
let () = msg_send![app, stop: nil];
// To stop event loop immediately, we need to post some event here.
post_dummy_event(app);
}
let _: () = msg_send![app, stop: nil];
// To stop event loop immediately, we need to post some event here.
post_dummy_event(app);
});
if window_count > 0 {
let window: id = msg_send![windows, objectAtIndex:0];
let window_has_focus = msg_send![window, isKeyWindow];
if !dialog_open && window_has_focus && dialog_is_closing {
HANDLER.dialog_is_closing.store(false, Ordering::SeqCst);
}
if dialog_open {
HANDLER.dialog_is_closing.store(true, Ordering::SeqCst);
}
}
};
}
HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() {
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (),
(ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (),
(old, new) if old == new => (),
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),
@@ -477,7 +457,7 @@ fn apply_activation_policy(app_delegate: &Object) {
let ns_app = NSApp();
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar won't be interactable.
// menu bar is initially unresponsive on macOS 10.15.
let act_pol = get_aux_state_mut(app_delegate).activation_policy;
ns_app.setActivationPolicy_(match act_pol {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,

View File

@@ -12,23 +12,28 @@ use std::{
};
use cocoa::{
appkit::{NSApp, NSEventType::NSApplicationDefined},
base::{id, nil, YES},
foundation::NSPoint,
appkit::{NSApp, NSEventModifierFlags, NSEventSubtype, NSEventType::NSApplicationDefined},
base::{id, nil, BOOL, NO, YES},
foundation::{NSInteger, NSPoint, NSTimeInterval},
};
use objc::rc::autoreleasepool;
use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{
app::APP_CLASS,
app_delegate::APP_DELEGATE_CLASS,
app_state::AppState,
monitor::{self, MonitorHandle},
observer::*,
util::IdRef,
platform::macos::ActivationPolicy,
platform_impl::{
get_aux_state_mut,
platform::{
app::APP_CLASS,
app_delegate::APP_DELEGATE_CLASS,
app_state::{AppState, Callback},
monitor::{self, MonitorHandle},
observer::*,
util::IdRef,
},
},
};
@@ -83,6 +88,11 @@ impl<T: 'static> EventLoopWindowTarget<T> {
let monitor = monitor::primary_monitor();
Some(RootMonitorHandle { inner: monitor })
}
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty())
}
}
impl<T> EventLoopWindowTarget<T> {
@@ -100,7 +110,9 @@ impl<T> EventLoopWindowTarget<T> {
}
pub struct EventLoop<T: 'static> {
pub(crate) delegate: IdRef,
/// The delegate is only weakly referenced by NSApplication, so we keep
/// it around here as well.
_delegate: IdRef,
window_target: Rc<RootWindowTarget<T>>,
panic_info: Rc<PanicInfo>,
@@ -111,13 +123,29 @@ pub struct EventLoop<T: 'static> {
/// Every other reference should be a Weak reference which is only upgraded
/// into a strong reference in order to call the callback but then the
/// strong reference should be dropped as soon as possible.
_callback: Option<Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>>,
_callback: Option<Rc<Callback<T>>>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) activation_policy: ActivationPolicy,
pub(crate) default_menu: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self {
activation_policy: Default::default(), // Regular
default_menu: true,
}
}
}
impl<T> EventLoop<T> {
pub fn new() -> Self {
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
let delegate = unsafe {
if !msg_send![class!(NSThread), isMainThread] {
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
if is_main_thread == NO {
panic!("On macOS, `EventLoop` must be created on the main thread!");
}
@@ -128,15 +156,21 @@ impl<T> EventLoop<T> {
let app: id = msg_send![APP_CLASS.0, sharedApplication];
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]);
let mut aux_state = get_aux_state_mut(&**delegate);
aux_state.activation_policy = attributes.activation_policy;
aux_state.default_menu = attributes.default_menu;
autoreleasepool(|| {
let _: () = msg_send![app, setDelegate:*delegate];
});
delegate
};
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info));
EventLoop {
delegate,
_delegate: delegate,
window_target: Rc::new(RootWindowTarget {
p: Default::default(),
_marker: PhantomData,
@@ -154,11 +188,11 @@ impl<T> EventLoop<T> {
where
F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
self.run_return(callback);
process::exit(0);
let exit_code = self.run_return(callback);
process::exit(exit_code);
}
pub fn run_return<F>(&mut self, callback: F)
pub fn run_return<F>(&mut self, callback: F) -> i32
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
@@ -175,29 +209,31 @@ impl<T> EventLoop<T> {
self._callback = Some(Rc::clone(&callback));
autoreleasepool(|| unsafe {
let exit_code = autoreleasepool(|| unsafe {
let app = NSApp();
assert_ne!(app, nil);
// A bit of juggling with the callback references to make sure
// that `self.callback` is the only owner of the callback.
let weak_cb: Weak<_> = Rc::downgrade(&callback);
mem::drop(callback);
drop(callback);
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
let () = msg_send![app, run];
let _: () = msg_send![app, run];
if let Some(panic) = self.panic_info.take() {
drop(self._callback.take());
resume_unwind(panic);
}
AppState::exit();
AppState::exit()
});
drop(self._callback.take());
exit_code
}
pub fn create_proxy(&self) -> Proxy<T> {
Proxy::new(self.window_target.p.sender.clone())
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender.clone())
}
}
@@ -208,15 +244,15 @@ pub unsafe fn post_dummy_event(target: id) {
event_class,
otherEventWithType: NSApplicationDefined
location: NSPoint::new(0.0, 0.0)
modifierFlags: 0
timestamp: 0
windowNumber: 0
modifierFlags: NSEventModifierFlags::empty()
timestamp: 0 as NSTimeInterval
windowNumber: 0 as NSInteger
context: nil
subtype: 0
data1: 0
data2: 0
subtype: NSEventSubtype::NSWindowExposedEventType
data1: 0 as NSInteger
data2: 0 as NSInteger
];
let () = msg_send![target, postEvent: dummy_event atStart: YES];
let _: () = msg_send![target, postEvent: dummy_event atStart: YES];
}
/// Catches panics that happen inside `f` and when a panic
@@ -240,7 +276,7 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
unsafe {
let app_class = class!(NSApplication);
let app: id = msg_send![app_class, sharedApplication];
let () = msg_send![app, stop: nil];
let _: () = msg_send![app, stop: nil];
// Posting a dummy event to get `stop` to take effect immediately.
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
@@ -251,14 +287,14 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
}
}
pub struct Proxy<T> {
pub struct EventLoopProxy<T> {
sender: mpsc::Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl<T: Send> Send for Proxy<T> {}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
impl<T> Drop for Proxy<T> {
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
@@ -266,13 +302,13 @@ impl<T> Drop for Proxy<T> {
}
}
impl<T> Clone for Proxy<T> {
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Proxy::new(self.sender.clone())
EventLoopProxy::new(self.sender.clone())
}
}
impl<T> Proxy<T> {
impl<T> EventLoopProxy<T> {
fn new(sender: mpsc::Sender<T>) -> Self {
unsafe {
// just wake up the eventloop
@@ -288,7 +324,7 @@ impl<T> Proxy<T> {
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
Proxy { sender, source }
EventLoopProxy { sender, source }
}
}

View File

@@ -15,7 +15,6 @@ use core_graphics::{
base::CGError,
display::{CGDirectDisplayID, CGDisplayConfigRef},
};
use objc;
pub const NSNotFound: NSInteger = NSInteger::max_value();
@@ -107,6 +106,8 @@ pub const kCGCursorWindowLevelKey: NSInteger = 19;
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
#[derive(Debug, Clone, Copy)]
#[repr(isize)]
#[allow(clippy::enum_variant_names)]
pub enum NSWindowLevel {
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
@@ -118,8 +119,6 @@ pub enum NSWindowLevel {
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
}
pub const NSStringEnumerationByComposedCharacterSequences: NSUInteger = 2;
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
@@ -165,14 +164,14 @@ pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut c_void;
#[cfg_attr(
not(use_colorsync_cgdisplaycreateuuidfromdisplayid),
link(name = "CoreGraphics", kind = "framework")
)]
#[cfg_attr(
use_colorsync_cgdisplaycreateuuidfromdisplayid,
link(name = "ColorSync", kind = "framework")
)]
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
// However, that framework was only introduced "publicly" in macOS 10.13.
//
// Since we want to support older versions, we can't link to `ColorSync`
// directly. Fortunately, it has always been available as a subframework of
// `ApplicationServices`, see:
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
}
@@ -222,3 +221,45 @@ extern "C" {
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
}
mod core_video {
use super::*;
#[link(name = "CoreVideo", kind = "framework")]
extern "C" {}
// CVBase.h
pub type CVTimeFlags = i32; // int32_t
pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CVTime {
pub time_value: i64, // int64_t
pub time_scale: i32, // int32_t
pub flags: i32, // int32_t
}
// CVReturn.h
pub type CVReturn = i32; // int32_t
pub const kCVReturnSuccess: CVReturn = 0;
// CVDisplayLink.h
pub type CVDisplayLinkRef = *mut c_void;
extern "C" {
pub fn CVDisplayLinkCreateWithCGDisplay(
displayID: CGDirectDisplayID,
displayLinkOut: *mut CVDisplayLinkRef,
) -> CVReturn;
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(
displayLink: CVDisplayLinkRef,
) -> CVTime;
pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef);
}
}
pub use core_video::*;

View File

@@ -1,4 +1,8 @@
#![cfg(target_os = "macos")]
#![allow(clippy::let_unit_value)]
#[macro_use]
mod util;
mod app;
mod app_delegate;
@@ -9,18 +13,19 @@ mod ffi;
mod menu;
mod monitor;
mod observer;
mod util;
mod view;
mod window;
mod window_delegate;
use std::{fmt, ops::Deref, sync::Arc};
pub use self::{
app_delegate::{get_aux_state_mut, AuxDelegateState},
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
pub(crate) use self::{
app_delegate::get_aux_state_mut,
event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoMode},
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
window::{PlatformSpecificWindowBuilderAttributes, UnownedWindow, WindowId},
};
use crate::{
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
@@ -65,7 +70,7 @@ impl Deref for Window {
}
impl Window {
pub fn new<T: 'static>(
pub(crate) fn new<T: 'static>(
_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,

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