Compare commits

...

240 Commits

Author SHA1 Message Date
Kirill Chibisov
e7037c6ad0 Release 0.27.4 version 2022-10-10 00:37:55 +03:00
Kirill Chibisov
51986bce26 On X11, fix IME crashing during reload
During reload we were picking old styles, but the styles could
change during reload leading to errors during IME building.

Fixes #2510.
2022-10-10 00:37:55 +03:00
Markus Siglreithmaier
429cc58f98 Windows, emit ReceivedCharacter on system keybinds
Currently needed for downstream users relaying on `ReceivedCharacter` for implementing
keybindings.
2022-10-10 00:37:55 +03:00
killian
81303d81a8 On Windows, fixed focus event emission on minimize. 2022-10-10 00:37:55 +03:00
Kirill Chibisov
adfa5bd275 Release 0.27.3 version 2022-09-11 19:59:13 +03:00
Kirill Chibisov
66319c571c On X11 query for XIM styles before creating IME
Fixes #2448.
2022-09-11 19:59:13 +03:00
Kirill Chibisov
f1470d1ab7 Send empty Ime::Preedit before the Ime::Commit
This should help downstream to automatically clear it.
2022-09-11 19:59:13 +03:00
Kirill Chibisov
79dc6bf4ab Remove automatic publish script
This script is confusing and provides no value especially
with release branches and patch fixes.
2022-09-11 19:59:13 +03:00
Kirill Chibisov
ad0520e935 Specify minimum supported version for RWH 0.4
Winit uses raw-window-handle of version 0.4.3,
but only 0.4.0 was specified.
2022-09-11 19:59:13 +03:00
Weng Xuetian
156fa375ea Clear preedit if there is no pending preedit on Wayland
Fixes #2478.
2022-09-11 19:59:13 +03:00
Marijn Suijten
2a2733b2d1 Revert "ci: manually point ANDROID_NDK_ROOT to latest supplied version"
This reverts commit 4895a29e92.

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

[1]: https://github.com/actions/runner-images/issues/5879#issuecomment-1197811704
2022-09-11 19:59:13 +03:00
Mads Marquart
7af1163ee7 iOS: Fix a few instances of UB (#2428)
* Fix iOS 32-bit

* Fix a few invalid message sends on iOS
2022-09-11 19:59:13 +03:00
ajtribick
229dbffa17 Windows: Update handling of system keypresses (#2445)
- Pass WM_SYSKEYDOWN to DefWindowProc
- Avoid intercepting WM_SYSCHAR to allow ALT+Space to work: removes ReceivedCharacter events for alt+keypress
- Intercept WM_MENUCHAR to disable bell sound
2022-09-11 19:59:13 +03:00
daxpedda
a5457b24c2 Document WindowEvent::Moved OS support (#2442) 2022-09-11 19:59:13 +03:00
ajtribick
5645bb1459 Disable default features in simple_logger
This fix CI building due to implicit rust version
bump in examples.
2022-09-11 19:59:13 +03:00
Sludge
77cd021c02 Document WindowEvent::Moved as unsupported on Wayland 2022-09-11 19:59:13 +03:00
Markus Siglreithmaier
2d732069a9 On Windows, improve support for undecorated windows (#2419) 2022-09-11 19:59:13 +03:00
Kirill Chibisov
2e4338bb8d Release 0.27.2 version 2022-08-12 14:39:44 +04:00
Kirill Chibisov
ec2888b8b7 On Wayland, fix Window::request_redraw being delayed
On Waylnad when asking for redraw before `MainEventsCleared`
would result for redraw being send on the next event loop tick,
which is not expectable given that it must be delivered on the same
event loop tick.
2022-08-12 11:54:02 +04:00
Kirill Chibisov
fa83bace12 Remove redundant steps from CI
Tests are already building the entire crate, so no need for a
separate builds slowing down the CI.
2022-08-11 19:31:11 +04:00
Kirill Chibisov
ee7dc48e3b Fix missleading breaking change on Windows
The applications should not rely on not-implemented behavior and
should use the right functions for that.
2022-08-11 19:07:40 +04:00
Robert Bragg
11d4a301e4 Implement version 0.4 of the HasRawWindowHandle trait
This makes Winit 0.27 compatible with crates like Wgpu 0.13 that are
using the raw_window_handle v0.4 crate and aren't able to upgrade to 0.5
until they do a new release (since it requires a semver change).

The change is intended to be self-contained (instead of pushing
the details into all the platform_impl backends) since this is only
intended to be a temporary trait implementation for backwards
compatibility that will likely be removed before the next Winit release.

Fixes #2415.
2022-08-11 18:33:02 +04:00
Mads Marquart
ad41eaf151 Add CODEOWNERS file (#2420)
* Add CODEOWNERS file

This makes it very clear when you're stepping down from the post as a maintainer, and makes it clear for users who is expected to review their PR

* Fix grammar

* Make @kchibisov receive pings for the X11 platform

* Fix typo

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2022-08-11 16:13:56 +02:00
ajtribick
9b71df9f97 On Windows, add opt-in function for device events (#2409) 2022-08-11 15:17:46 +02:00
Mads Marquart
b1c9e4a6fa Fix tracking of phase changes for mousewheel on trackpad (#2158) 2022-08-10 18:28:19 +02:00
Anton Bulakh
cdbaf4816a On X11, fix window hints not persisting
This commit fixes the issue with min, max, and resize increments
not persisting across the dpi changes.
2022-08-08 18:27:11 +04:00
Amr Bashir
6b7ceedc91 Windows: respect min/max sizes when creating the window (#2393) 2022-08-04 23:03:55 +02:00
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
Kirill Chibisov
ea1c031b54 Release 0.26.0 version 2021-12-01 14:43:38 +03:00
Mads Marquart
11a44081df macOS move impl details of platform into platform_impl 2021-12-01 14:20:56 +03:00
Mads Marquart
5eb9c9504b Update raw-window-handle to 0.4.1 (#1957)
* Update raw-window-handle to `0.4.2`

See:
- https://github.com/rust-windowing/raw-window-handle/issues/72
- https://github.com/rust-windowing/raw-window-handle/pull/73
- https://github.com/rust-windowing/raw-window-handle/pull/74

* Clean up raw_window_handle functions a bit
2021-11-30 17:50:23 +01:00
Adrien Bennadji
29a078f65c bump ndk dependencies to 0.5 (#2071) 2021-11-24 16:56:57 +01:00
Alphyr
be61ca13fe On X11, update 'mio' to 0.8 2021-11-20 03:42:23 +03:00
mahkoh
e9d5b2007a On X11, don't panic when getting EINTR
Fixes #1972.
2021-11-20 03:24:45 +03:00
Ian Hobson
f2de8475fc Show the menu bar in borderless fullscreen on macOS (#2053)
* In MacOS, only disable menu bar in exclusive fullscreen

* Save and restore fullscreen options in set_fullscreen

* Don't always cache presentation options when entering exclusive fullscreen

This commit caches presentation options when entering exclusive fullscreen
only if we're coming from borderless fullscreen.

Then, when transitioning from exclusive -> borderless, if no cached presentation
options are present, then the default borderless options are applied.

This fixes the menu bar being unavailable when taking the following path:
[not fullscreen] -> [exclusive fullscreen] -> [borderless fullscreen].

Without this commit, the presentation options from [not fullscreen] were being
cached and then applied to [borderless fullscreen].

* Restore the window level when switching to exclusive fullscreen

The hack of using `CGShieldingWindowLevel() + 1` in borderless fullscreen needs
to be undone when switching from [borderless] -> [exclusive] fullscreen,
otherwise there are menu bar glitches when following a path through
[borderless] -> [exclusive] -> [borderless] modes.

Now, this might appear to conflict with the 'always on top' feature which uses
the 'floating window' level, but this feature appears to be broken anyway when
entering and exiting fullscreen with an always-on-top window. So, rather than
introducing logic to attempt to restore to the 'floating' level here, I think
it's better to do the simple thing for now and then introduce logic for
always-on-top windows when fixing the overall fullscreen behaviour.

* Update the changelog

Co-authored-by: Ehden Sinai <ehdens@gmail.com>
Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>
2021-11-19 13:05:36 -08:00
Markus Siglreithmaier
3ecbea3c39 Windows: Split window initialization across NCCREATE and CREATE (#2062)
* Refactor window initialization by splitting NCCREATE and CREATE related tasks.

Fixes issue with invisible owner windows.

* address review comments

* Update src/platform_impl/windows/event_loop.rs

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

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-11-17 18:33:44 +01:00
cmeissl
c4df7ad7a5 On Wayland, commit the window surface after setting the decoration mode
Fixes #2064.
2021-11-15 01:23:54 +03:00
Emil Ernerfeldt
387567a917 macOS: Fix native file dialogs freezing the event loop (#2027)
* macOS: Ignore all events while in the callback

Previously all native dialogs, such as [rfd](https://github.com/PolyMeilex/rfd), would cause the event loop (event_loop.run) to freeze.

* Update changelog

* spelling

* Fix merge mistake

* Add link to issue in the code

Co-authored-by: Poly <marynczak.bartlomiej@gmail.com>
2021-11-04 11:37:02 -07:00
Markus Siglreithmaier
cfbe8462cc Windows: Increase wait timer resolution (#2007)
Windows: Increase wait timer resolution for more accurate timing when using `WaitUntil`.
2021-11-02 21:51:39 +01:00
Markus Siglreithmaier
5f4df54895 Android: Bump ndk/ndk-glue version (#2047) 2021-11-02 18:54:59 +01:00
Kirill Chibisov
ed698f2462 On Wayland, add wayland-dlopen feature to use dlopen
While winit was always using dlopen for opening system libs, it
provides a way now to disable dlopen feature helping with linking
on some targets.

Fixes #2037.
2021-10-31 17:06:00 +03:00
Kirill Chibisov
b4774861db On X11, if RANDR based scale factor is higher than 20 reset it to 1
Some video drivers could set display metrics to odd values, which can result in
extra large scale factors (e.g. winit is sending 720 for 5k screen on nvidia
binary drivers), so let's just drop them to prevent clients from using them.
The value 20 was picked, because the DPR for 8k @ 5 inch is ~18.36.

Fixes #1983.
2021-10-30 23:47:31 +03:00
Alexis Hildebrandt
9768f73bb7 Update smithay-client-toolkit
Fixes libxcbcommon linking on OpenBSD by using pkg-config when
building without dlopen feature. For details see [1].

[1] - https://github.com/Smithay/client-toolkit/pull/198
2021-10-30 22:35:36 +03:00
Arthur Kaukal Valladares
805249e27e Android: Window::config now initially returns accurate data. (#2021)
Initialize Android's static 'CONFIG' from NativeActivity's Asset Manager
2021-10-26 23:28:28 +02:00
Markus Siglreithmaier
5f24c40d05 Fix wasm CI failure (#2024)
* Fix wasm ci

* remove cargo-web step
2021-10-26 11:05:01 -07:00
Philippe Renon
1b3b82a3c1 Clippy fixes (#2011)
* windows: bump winapi version

* windows: address dark_mode FIXMEs

use now available winapi structures

* clippy: fix clippy::upper_case_acronyms warnings

* clippy: fix needless_arbitrary_self_type warnings

* clippy: fix clone_on_copy warnings

* clippy: fix unnecessary_mut_passed warnings

* clippy: fix identity_op warnings

* clippy: fix misc warnings

* prefix rustdoc lints with rustdoc::

the prefix was introduced in Rust 1.52

* windows: silence file_drop_handler is never read warning

* clippy: fix from_over_into warnings

and a bit of naming simplification

* clippy: fix missing_safety_doc warnings

* make dummy() functions const
2021-08-30 19:40:02 +02:00
Davester47
9e72396709 Fix X11 memory leak and remove mio-misc (#1987)
* Fix X11 memory leak and remove mio-misc

I also fixed a couple of clippy lints.
Fixes #1984

* Send the redraw event before waking up the main event

* Use .map instead of a match, and remove comments saved by git

* Remove unnecessary pub keywords on `WakeSender` in x11/mod.rs
2021-08-24 12:38:56 +02:00
oxalica
125ee0b446 Emit ScaleFactorChanged event on monitor reconnect (#1963)
When disconnect the only monitor, scale factor is reset to 1.0. We need
to set it back when the monitor is reconnected.

We previously assume current window must be on an existing monitor, but
that's not true in case of reconnecting the only one monitor.
2021-08-24 12:36:13 +02:00
sandmor
3bfb580d7a Fix potential bug (#2009)
* polish and failed to find visual warning

* improvement
2021-08-24 12:35:11 +02:00
sandmor
b54d47796d Fix transparency on X11 (#2006)
* find transparent in x11

* remove debug hooks

* update changelog

* polish and failed to find visual warning
2021-08-22 20:45:24 +02:00
Kirill Chibisov
b5d0d6ff3e On Wayland, implement 'request_user_attention'
This commit implements 'request_user_attention' on Wayland with
new 'xdg_activation_v1' protocol.
2021-08-17 07:59:57 +03:00
Kirill Chibisov
c9520deef8 Update smithay-client-toolkit to 'v0.15.0'
This commit also drops 'Theme' trait with its support types
in favor of 'FallbackFrame' meaning that winit will use some
predefined frame for the time being, since porting 'ConceptFrame'
will require adding font rendering librarires right into winit,
which is not desired.

Fixes #1889.
2021-08-15 22:31:59 +03:00
Steven Bosnick
ceab0f8c40 On Wayland, log error for failure to set cursor
The inability to set the cursor using any of the named cursor files
likely indicates an error in the system on which we are running.
'WinitPointer::set_cursor' also does not have any way of returning an
error so just log the error to assist users in diagnosing the problem.

Fixes: #1988.
2021-08-15 22:05:14 +03:00
Markus Siglreithmaier
b87757c552 Fix Window visibility regression (#1994)
On Windows, set windows visible on init before position update

Ensure that the style change is correctly applied, triggering the necessary events.
2021-08-12 18:57:07 +02:00
Nuno Ribeiro
1972eb952d Adds Android winit<->ndk_glue version match table (#1993)
* Adds Android winit<->ndk_glue version match table

* Fixes justification

* Adds crosses

* Address review and instead of a m:n table it shows a 1:1 version compatibility

* Addresses review
2021-08-11 14:28:49 -07:00
Osspial
f92803d80e Prevent ghost window from showing up on taskbar (#1977)
Add WS_EX_TOOLWINDOW to event target window
2021-08-11 20:02:40 +02:00
Yusuke Kominami
8afeb910bd Add composition event on macOS (#1979)
* Enable to show text when IME is active

* Remove unnecessary variable

* Enable to use IME

* fmt

* Remove println! for debug

* Fix handling of utf-8 string

* clear_marked_text should be rust function, not member function

* Store state information in ViewState

* Remove unnecessary function

* format

* Remove mut

* format

* Remove duplicate marked text

* Remove unused `is_preediting` field

Co-authored-by: Artúr Kovács <kovacs.artur.barnabas@gmail.com>
2021-08-09 11:14:13 +02:00
Sven Niederberger
f16ed98af4 On X11 and Wayland, fx window resize cursor orientation
Fixes #1912.
2021-08-01 11:19:00 +03:00
Aidan Dang
2a9916103b On Wayland, load "hand2" and "hand1" cursor icons for CursorIcon::Hand 2021-08-01 11:16:02 +03:00
Markus Røyset
63ad47a7bf Remove window subclassing (#1933)
* Remove window subclassing

* Always call `DefWindowProcW` when we don't process a message

* Improve window initialization

Note that the error path in `init` is kind of cursed at the moment.

* Rename `ThreadMsgTargetCallbackData` to `ThreadMsgTargetData`

* Simplify window initialization

* Fix compilation on 32-bit targets

* Simplify the creation of the event target window

* Use `.clone()` rather than `Rc::clone()`

* Use concrete types for args to `SetWindowLongPtrW`

On 32-bit targets, `SetWindowLongPtrW` is an alias to `SetWindowLongW`,
which returns `LONG` (`i32`) rather than `LONG_PTR` (`isisze`).

* Minor comment adjustments
2021-07-16 12:40:48 +02:00
jim jammer
27e6548343 macOS: Remove is_key_down from ViewState (#1489)
Fixes #1488.

`is_key_down` was only set to true when `insertText` was called.
Therefore `is_key_down` was actually meant to store whether the most
recently pressed key generated an `insertText` event, at which, winit
produces a `ReceivedCharacter`. The issue is that `insertText` is *not*
called for "key repeat", but winit wants to send `ReceivedCharacter`
events for "key repeat" too. To solve this, the `is_key_down` variable
was then checked in the `key_down` function to determine whether it was
valid to produce repeated `ReceivedCharacter` events during "key
repeat". However this check is not needed since `ReceivedCharacter` must
always be called if the key event has a non-empty `characters` property.

Furthermore `is_key_down` didn't actually store whether the previously
pressed character had an `insertText` event, because it was incorrectly
set to false on every "key up". This meant that if two keys were pressed
consecutively and then the first was released, then `is_key_down` was
set to false even if the most recent keypress did actually produce an
`insertText`.

Update changelog.
2021-07-13 17:38:01 +02:00
Markus Røyset
8c91986dd3 Remove libc dependency on non-linux platforms (#1976) 2021-07-13 17:27:47 +02:00
Aden Haussmann
5a65347c4e Changed description of window scale factor in Events (#1834)
* changed description of window scale factor in Events

* Update src/dpi.rs

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

* Fix intra-doc link

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-06-16 09:24:49 +02:00
Francesca Lovebloom
635180c8be Update Hall of Champions (#1959)
* Lionize Aubrey

* Lionize Victor

* Lionize Freya

* Stylization consistency
2021-06-14 13:55:13 -07:00
Onirik79
019ce9862f Fix typo in events documentation (#1960) 2021-06-13 14:26:20 +02:00
Artúr Kovács
c7f46876a7 Drop the event callback before exiting on macOS (#1954)
* Drop the event callback before exiting

* Update the changelog

* Apply suggestion from review

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

* Apply review suggestions

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-06-13 14:25:06 +02:00
garasubo
c916eb6137 On X11 and Wayland, add is_maximized support
Fixes #1845.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2021-06-10 10:43:27 +03:00
Tilmann Meyer
67cca71524 Implement Window::request_redraw on Android (#1953) 2021-06-05 12:47:08 +02:00
i509VCB
1eff7ae004 Document how main_thread_id for Windows works (#1951) 2021-06-05 12:46:44 +02:00
Mads Marquart
982ad46c83 Use objc's autoreleasepool instead of manually with NSAutoreleasePool (#1936)
Ensures the pools are released even if we panic
2021-05-27 17:38:41 +02:00
Markus Røyset
657b4fd59e Remove support for stdweb (#1941)
* Remove support for `stdweb`

* Expunge `stdweb`; make `web-sys` the default

* Mark this change as a breaking change

* Re-insert accidental removal of space

* Use the correct cargo feature syntax

* Re-add some `cfg` attributes

* Remove `web-sys` feature from CI
2021-05-24 10:06:21 -07:00
Max de Danschutter
b371b406d5 Implemented focus_window (#1944) 2021-05-19 18:39:53 +02:00
Artúr Kovács
91591c4e94 Release 0.25.0 (#1939) 2021-05-15 19:17:08 +02:00
Luis Wirth
078b9719cc implement mint conversions (#1930)
Implement conversions for [mint](https://docs.rs/mint) (math interoperability standard types).

- `impl From<mint::Point2> for {Physical, Logical}Position`
- `impl From<{Physical, Logical}Position> for mint::Point2`

- `impl From<mint::Vector2> for {Physical, Logical}Size`
- `impl From<{Physical, Logical}Size> for mint::Vector2`
2021-05-09 00:56:52 +02:00
Markus Røyset
41d9826ee9 Fix incorrect changelog entry for #1524 (#1916)
* Fix incorrect changelog entry for #1524

* Fix incorrect changelog entry for #1524
2021-05-04 11:00:11 -07:00
Artúr Kovács
0152508a39 Allow preventing the creation of the default menu (#1923)
* Allow preventing the creation of the default menu

* Use more grammar friendly naming

* Update the changelog
2021-04-30 13:34:50 +02:00
Artúr Kovács
cdeb1c3828 Require setting the activation policy on the event loop (#1922)
* Require setting the activation policy on the event loop

* Run cargo fmt

* Update changelog

* Fixes and tweaks from review

* Correct comment in app_state.rs

Co-authored-by: Mads Marquart <mads@marquart.dk>
2021-04-30 11:31:28 +02:00
z4122
0986fae066 Add accept_first_mouse for macOS (#1882)
* feat: add accept_first_mouse for macOS

* Update the changelog

Co-authored-by: Artur Kovacs <kovacs.artur.barnabas@gmail.com>
2021-04-30 11:30:09 +02:00
Mads Marquart
277515636d MacOS: Only activate after the application has finished launching (#1903)
* MacOS: Only activate after the application has finished launching

This fixes the main menu not responding until you refocus, at least from what I can tell - though we might have to do something similar to https://github.com/linebender/druid/pull/994 to fix it fully?

* MacOS: Remove activation hack

* Stop unnecessarily calling `makeKeyWindow` on initially hidden windows

You can't make hidden windows the key window

* Add new, simpler activation hack

For activating multiple windows created before the application finished launching
2021-04-29 19:49:17 +02:00
Mads Marquart
45aacd8407 Use initialFirstResponder instead of makeFirstResponder (#1920)
As recommended by the documentation: https://developer.apple.com/documentation/appkit/nswindow/1419366-makefirstresponder?language=objc
2021-04-29 12:52:41 +02:00
Casper Rogild Storm
e8cdf8b092 Add MacOS menu (#1583)
* feat: added MacOS menu

* fix: ran fmt

* extracted function into variable

* idiomatic formatting

* Set the default menu only during app startup

* Don't set the activation policy in the menu init

Co-authored-by: Artur Kovacs <kovacs.artur.barnabas@gmail.com>
2021-04-24 16:56:46 +02:00
Artúr Kovács
1c4d6e7613 Correct the false documentation about macOS dpi (#1905) 2021-04-13 21:31:41 +02:00
LoganDark
04b4e48265 Derive Default, Hash, and Eq for some dpi types (#1833)
* Derive more things

* Changelog entry
2021-04-12 23:12:39 +02:00
Rodrigodd
dabcb1834d On Windows, allow the creation of popup window (#1895)
Add with_owner_window to WindowBuilderExtWindows.
Add set_enable to WindowExtWindows.
2021-04-10 15:47:19 +02:00
Mads Marquart
629cd86c7c Stop calling NSApplication.finishLaunching on window creation (#1902)
This is called internally by NSApplication.run, and is not something we should call - I couldn't find the reasoning behind this being there in the first place, git blame reveals c38110cac from 2014, so probably a piece of legacy code.

Removing this fixes creating new windows when you have assigned a main menu to the application.
2021-04-07 22:24:49 +02:00
Xiaopeng Li
ba704c4eb4 Mac: Redraw immediately to prevent shaking on window resize (#1901)
* Mac: Redraw immediately to prevent shaking on window resize

* Update CHANGELOG.md

* Update CHANGELOG.md

Co-authored-by: 李小鹏 <lixiaopeng.jetspark@bytedance.com>
Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-04-06 09:22:38 +02:00
Aleksandr Ovchinnikov
0487876826 On macOS, wake up the event loop immediately when a redraw is requested. (#1812)
We allow to have RunLoop running only on the main thread. Which means if
we call Window::request_redraw() from other the thread then we have to
wait until some other event arrives on the main thread. That situation
is even worse when we have ControlFlow set to the `Wait` mode then user
will not ever render anything.
2021-04-06 09:19:25 +02:00
Markus Røyset
ca9c05368e Fix CI warnings (#1898)
* Fix CI warnings

* Use the panic! macro rather than format! + panic_any
2021-03-30 21:27:32 +02:00
Michal Srb
0d634a0061 Add WindowBuilder::with_outer_position (#1866) 2021-03-25 19:18:51 +01:00
Norbert Nemec
86748fbc68 Fix communication of fractional RI_MOUSE_WHEEL events (Windows) (#1877) 2021-03-11 22:08:29 +01:00
Artúr Kovács
599477d754 Only try publishing when a version tag is pushed (#1876) 2021-03-10 14:10:35 -08:00
daxpedda
889258f538 Upgrade mio to 0.7 (#1875)
* Upgrade `mio` to 0.7
Replaced `mio-extras` with `mio-misc`.

* Possible improvement

* Remove leftover

* Wrong rebase

* Fix typo
2021-03-09 09:50:15 -07:00
Artúr Kovács
ffe2143d14 Fix for closure-captured values not being dropped on panic (#1853)
* Fix for #1850

* Update changelog

* Fix for compilation warnings

* Apply suggestions from code review

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

* Improve code quality

* Change Arc<Mutex> to Rc<RefCell>

* Panicking in the user callback is now well defined

* Address feedback

* Fix nightly warning

* The panic info is now not a global.

* Apply suggestions from code review

Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>

* Address feedback

Co-authored-by: Markus Røyset <maroider@protonmail.com>
Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>
2021-03-08 19:56:39 +01:00
daxpedda
98470393d1 Add dragging window with cursor feature (#1840)
* X11 implementation.

* Introduce example.

* Wayland implementation.

* Windows implementation.

* Improve Wayland seat passing.

* MacOS implementation.

* Correct windows implementation per specification.

* Update dependency smithay-client-toolkit from branch to master.

* Fixed blocking thread in windows implementation.

* Add multi-window example.

* Move Wayland to a different PR.

* Fix CHANGELOG.

* Improve example.

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

* Rename `set_drag_window` to `begin_drag`.

* Improve example.

* Fix CHANGELOG.

* Fix CHANGELOG.

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

* Rename to `drag_window`.

* Fix typo.

* Re-introduce Wayland implementation.

* Fixing Wayland build.

* Fixing Wayland build.

* Move SCTK to 0.12.3.

Co-authored-by: Markus Røyset <maroider@protonmail.com>
2021-03-07 10:43:23 +01:00
Artúr Kovács
4192d04a53 Fix seg-fault when using without a window (#1874)
* Fix seg-fault when using without a window #1869

* Update changelog
2021-03-06 11:17:23 +01:00
leafjolt
3571dcd68c Update window.rs (#1871) 2021-02-27 21:25:26 +01:00
mmacedo
952edcb804 Android: Add KeyEvent handling (#1839) 2021-02-23 22:35:38 +01:00
Axel Cocat
10a94c0794 Fix Windows' try_theme returning Theme::Dark when new theme is light (#1861) 2021-02-20 00:06:12 +01:00
Björn Steinbrink
dd32ace9ab Restore the ability to have fully transparent windows on Windows (#1815)
* Restore the ability to have fully transparent windows on Windows

Besides its original purpose, commit 6343059b "Fix Windows transparency
behavior to support fully-opaque regions (#1621)" also included some
changes considered cleanups, one of them was:

* Remove the `CreateRectRgn` call, since we want the entire window's region to
  have blur behind it, and `DwnEnableBlurBehindWindow` does that by default.

But the original code actually disabled the blur effect for the whole
window by creating an empty region for it, because that allows for the
window to be truely fully transparent. With the blur effect in place,
the areas meant to be transparent either blur the things behind it
(until Windows 8) or are darkened (since Windows 8). This also means
that on Windows 8 and newer, the resulting colors are darker than
intended in translucent areas when the blur effect is enabled.

This restores the behaviour from winit <0.24 and fixes #1814.

Arguably, one might want to expose the ability to control the blur
region, but that is outside the scope of this commit.

* Remove useless WS_EX_LAYERED from transparent windows on Windows

`WS_EX_LAYERED` is not supposed to be used in combination with
`CS_OWNDC`. In winit, as it is currently used, `WS_EX_LAYERED` actually
has no effect at all. The only relevant call is to
`SetLayeredWindowAttributes`, which is required to make the window
visible at all with `WS_EX_LAYERED` set, but is called with full
opacity, i.e. there's no transparency involved at all.

The actual transparency is already achieved by using
`DwmEnableBlurBehindWindow`, so `WS_EX_LAYERED` and the call to
`SetLayeredWindowAttributes` can both be removed.
2021-02-17 13:50:24 +01:00
Will Crichton
7e0c6ee097 Add DeviceEvent::MouseMove on web platform to support pointer lock (#1827)
* Add DeviceEvent::MouseMove on web platform to support pointer lock

* Update changelog

* Add support for stdweb too

* Add mouse_delta to stdweb

* Remove reference to pointer lock
2021-02-16 17:50:46 -05:00
Ssaely
b1be34c6a0 fix cursor blinking when clicking decorations bar on Windows (#1852) 2021-02-06 21:10:36 +01:00
Imberflur
b9307a9967 Change linking of CGDisplayCreateUUIDFromDisplayID on macos (#1626)
* Link CGDisplayCreateUUIDFromDisplayID through ColorSync instead of CoreGraphics

* Conditionally link through ColorSync only if WINIT_LINK_COLORSYNC is set
to true

* Document new macos env var in README
2021-02-05 08:58:55 +01:00
Mads Marquart
b1d353180b Add ability to assign a menu when creating a window on Windows (#1842) 2021-02-04 22:26:33 +01:00
Marijn Suijten
bd99eb1347 Android: Bump ndk/ndk-glue to 0.3 and use constants for event ident (#1847)
Following the changes in [1] this bumps ndk and ndk-glue to 0.3 and uses
the new constants. The minor version has been bumped to prevent
applications from running an older winit (without #1826) with a newer
ndk/ndk-glue that does not pass this `ident` through the `data` pointer
anymore.

[1]: https://github.com/rust-windowing/android-ndk-rs/pull/112
2021-01-30 19:43:26 +01:00
Mads Marquart
f79c01b0cf Fix HINSTANCE returned by raw_window_handle on 64 bit Windows (#1841) 2021-01-28 18:51:49 +01:00
Simas Toleikis
3f1e09ec0e Add Window::is_maximized method (#1804) 2021-01-27 19:01:17 +01:00
Markus Røyset
05125029c6 On Windows, fix deadlock caused by mouse capture (#1830)
The issue was caused by calling SetCapture on a window which already
had the capture. The WM_CAPTURECHANGED handler assumed that it would
only run if the capture was lost, but that wasn't the case. This made
the handler to try to lock the window state mutex while it was already
locked.

The issue was introduced in #1797 (932cbe4).
2021-01-19 17:41:02 +01:00
Marijn Suijten
05fe983757 android: Use event identifier instead of userdata pointer (#1826)
ndk-glue currently sets both the `ident` field and user-data pointer to
`0` or `1` for the event pipe and input queue respectively, to tell
these sources apart. While it works to reinterpret this `data` pointer
as integer identifier it shouldn't be abused for that, in particular
when one may wish to provide extra information with an event in the
future; then the `data` field is used as pointer (or abused as abstract
value) for that.
2021-01-13 23:02:55 +01:00
alula
d1a7749df5 Android: Do not mark unhandled events as handled. (#1820) 2021-01-12 08:25:56 +01:00
Markus Røyset
9d63fc7ca0 On Windows, set the cursor icon when the cursor first enters a window (#1807) 2021-01-05 17:39:13 +01:00
Markus Røyset
38fccebe1f On Windows, change the default window size (#1805) 2020-12-20 17:59:46 +01:00
Markus Røyset
c05952b813 On Windows, improve handling of window destruction (#1798) 2020-12-20 12:54:42 +01:00
Samuel
932cbe40bf On Windows, fix bug causing mouse capture to not be released. (#1797) 2020-12-15 07:31:13 +01:00
relrelb
39573d65d0 Windows: Preserve minimized/maximized state in fullscreen (#1784) 2020-12-13 19:06:53 +01:00
156 changed files with 11262 additions and 6940 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

35
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,35 @@
# Core maintainers:
# - @msiglreith
# - @kchibisov
# - @madsmtm
# - @maroider
# Android
/src/platform/android.rs @msiglreith
/src/platform_impl/android @msiglreith
# iOS
/src/platform/ios.rs @francesca64
/src/platform_impl/ios @francesca64
# Unix in general
/src/platform/unix.rs @kchibisov
/src/platform_impl/linux/mod.rs @kchibisov
# Wayland
/src/platform_impl/linux/wayland @kchibisov
# X11
/src/platform_impl/linux/x11 @kchibisov
# macOS
/src/platform/macos.rs @madsmtm
/src/platform_impl/macos @madsmtm
# Web (no maintainer)
/src/platform/web.rs
/src/platform_impl/web
# Windows
/src/platform/windows.rs @msiglreith
/src/platform_impl/windows @msiglreith

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 }
@@ -38,23 +32,24 @@ jobs:
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: wayland }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web }
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web }
- { target: wasm32-unknown-unknown, os: windows-latest, }
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:
@@ -70,26 +65,22 @@ jobs:
with:
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install Linux dependencies
if: (matrix.platform.os == 'ubuntu-latest')
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo 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: Install cargo-web
continue-on-error: true
if: contains(matrix.platform.target, 'wasm32')
run: cargo install cargo-web
- 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
- name: Build
shell: bash
run: cargo $CMD build --verbose --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 tests
shell: bash
@@ -102,10 +93,10 @@ jobs:
!contains(matrix.platform.target, 'wasm32'))
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build with serde enabled
- name: Lint with clippy
shell: bash
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
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 tests with serde enabled
shell: bash

View File

@@ -1,18 +0,0 @@
name: Publish
on:
push:
branches: [master]
paths: "Cargo.toml"
jobs:
Publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.cratesio_token }}

1
.gitignore vendored
View File

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

View File

@@ -1,25 +1,208 @@
# 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.4
- On Windows, emit `ReceivedCharacter` events on system keybindings.
- On Windows, fixed focus event emission on minimize.
- On X11, fixed IME crashing during reload.
# 0.27.3 (2022-9-10)
- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window.
- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled.
- On Windows, fixed ALT+Space shortcut to open window menu.
- On Wayland, fixed `Ime::Preedit` not being sent on IME reset.
- Fixed unbound version specified for `raw-window-handle` leading to compilation failures.
- Empty `Ime::Preedit` event will be sent before `Ime::Commit` to help clearing preedit.
- On X11, fixed IME context picking by querying for supported styles beforehand.
# 0.27.2 (2022-8-12)
- On macOS, fixed touch phase reporting when scrolling.
- On X11, fix min, max and resize increment hints not persisting for resizable windows (e.g. on DPI change).
- On Windows, respect min/max inner sizes when creating the window.
- For backwards compatibility, `Window` now (additionally) implements the old version (`0.4`) of the `HasRawWindowHandle` trait
- On Windows, added support for `EventLoopWindowTarget::set_device_event_filter`.
- On Wayland, fix user requested `WindowEvent::RedrawRequested` being delayed by a frame.
# 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). 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.
- On X11, select an appropriate visual for transparency if is requested
- On Wayland and X11, fix diagonal window resize cursor orientation.
- On macOS, drop the event callback before exiting.
- On Android, implement `Window::request_redraw`
- **Breaking:** On Web, remove the `stdweb` backend.
- Added `Window::focus_window`to bring the window to the front and set input focus.
- On Wayland and X11, implement `is_maximized` method on `Window`.
- On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`.
- On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events.
- On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`.
- **Breaking:** On Wayland, Theme trait and its support types are dropped.
- On Wayland, bump `smithay-client-toolkit` to 0.15.1.
- On Wayland, implement `request_user_attention` with `xdg_activation_v1`.
- On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected.
- On X11, if RANDR based scale factor is higher than 20 reset it to 1
- On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries.
- **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5.
- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`.
- On macOS, fix native file dialogs hanging the event loop.
- On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland`
- On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode.
- On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup.
# 0.25.0 (2021-05-15)
- **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy`
- On macOS, wait with activating the application until the application has initialized.
- On macOS, fix creating new windows when the application has a main menu.
- On Windows, fix fractional deltas for mouse wheel device events.
- On macOS, fix segmentation fault after dropping the main window.
- On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode.
- Added `is_maximized` method to `Window`.
- On Windows, fix bug where clicking the decoration bar would make the cursor blink.
- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor.
- On macOS, wake up the event loop immediately when a redraw is requested.
- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600).
- On Windows, fix bug causing mouse capture to not be released.
- On Windows, fix fullscreen not preserving minimized/maximized state.
- On Android, unimplemented events are marked as unhandled on the native event loop.
- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time.
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
- On X11, bump `mio` to 0.7.
- On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows.
- On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows.
- On macOS, emit `RedrawRequested` events immediately while the window is being resized.
- Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`.
- On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`)
- On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click".
- Implement mint (math interoperability standard types) conversions (under feature flag `mint`).
# 0.24.0 (2020-12-09)
- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory.
- On Windows, implement `Window::set_ime_position`.
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`.
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
- On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag.
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag.
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.
- **Breaking:** On Windows, include prefix byte in scancodes.
- On Wayland, fix window not being resizeable when using `with_min_inner_size` in `WindowBuilder`.
- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`.
- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland.
- On Windows, fix use after free crash during window destruction.
- On Windows, fix use-after-free crash during window destruction.
- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input.
- On macOS, fix compilation when targeting aarch64
- On macOS, fix compilation when targeting aarch64.
- On X11, fix `Window::request_redraw` not waking the event loop.
- On Wayland, the keypad arrow keys are now recognized.
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`.
- Added `request_user_attention` method to `Window`.
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
- On Wayland, default font size in CSD increased from 11 to 17.
- On Windows, fix bug causing message boxes to appear delayed.
- On Android, support multi-touch.
@@ -34,7 +217,7 @@
- On X11, fix deadlock when calling `set_fullscreen_inner`.
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling
- On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`.
- On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
- On android added support for `run_return`.
@@ -107,7 +290,6 @@
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
- On Web, fix a possible panic during event handling
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
- On Windows, drag and drop can now be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
# 0.22.0 (2020-03-09)
@@ -152,13 +334,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)
@@ -263,7 +445,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.
@@ -462,7 +644,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)
@@ -612,7 +794,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,25 @@ 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.
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
If you are interested in contributing or testing on a platform, please add yourself to that table!
If you are interested in being pinged when testing is needed for a certain platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
## Making a new release
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,45 +1,64 @@
[package]
name = "winit"
version = "0.24.0"
version = "0.27.4"
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", "web-sys"]
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"]
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
stdweb = ["std_web", "instant/stdweb"]
x11 = ["x11-dl", "mio", "mio-extras", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "sctk"]
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 = "0.1"
lazy_static = "1"
libc = "0.2.64"
instant = { version = "0.1", features = ["wasm-bindgen"] }
once_cell = "1.12"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
raw-window-handle = "0.3"
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
raw_window_handle_04 = { package = "raw-window-handle", version = "0.4.3" }
bitflags = "1"
mint = { version = "0.5.6", optional = true }
[dev-dependencies]
image = "0.23.12"
simple_logger = "1.9"
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "2.1.0", default_features = false }
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.2.0"
ndk-sys = "0.2.0"
ndk-glue = "0.2.0"
# 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"
@@ -50,52 +69,52 @@ core-foundation = "0.9"
core-graphics = "0.22"
dispatch = "0.2.0"
[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.6"
[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",
"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.28", features = [ "dlopen"] , optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.12", optional = true }
mio = { version = "0.6", optional = true }
mio-extras = { version = "2.0", 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]
package = "web-sys"
version = "0.3.22"
optional = true
features = [
'console',
"AddEventListenerOptions",
@@ -121,13 +140,11 @@ features = [
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.45"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
package = "stdweb"
version = "=0.4.20"
optional = true
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
[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.
@@ -116,6 +118,7 @@ If your PR makes notable changes to Winit's features, please update this section
### Windows
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
@@ -171,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**|
@@ -182,11 +185,9 @@ Legend:
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ \*1|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
\*1: `WindowEvent::ScaleFactorChanged` is not sent on `stdweb` backend.
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
@@ -197,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 |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
@@ -208,6 +211,7 @@ Legend:
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.

View File

@@ -1,14 +1,26 @@
# Hall of Champions
The Winit maintainers would like to recognize the following former Winit
contributors, without whom Winit would not exist in its current form. We thank
The winit maintainers would like to recognize the following former winit
contributors, without whom winit would not exist in its current form. We thank
them deeply for their time and efforts, and wish them best of luck in their
future endeavors:
* [@tomaka]: For creating the Winit project and guiding it through its early
* [@tomaka]: For creating the winit project and guiding it through its early
years of existence.
* [@vberger]: For diligently creating the Wayland backend, and being its
extremely helpful and benevolent maintainer for years.
* [@francesca64]: For taking over the responsibility of maintaining almost every
Winit backend, and standardizing HiDPI support across all of them
winit backend, and standardizing HiDPI support across all of them.
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
vastly more sustainable era of winit.
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
stellar dedication to improving both winit and glutin.
* [@ArturKovacs]: For consistently maintaining the macOS backend, and his
immense involvement in designing and implementing the new keyboard API.
[@tomaka]: https://github.com/tomaka
[@vberger]: https://github.com/vberger
[@francesca64]: https://github.com/francesca64
[@Osspial]: https://github.com/Osspial
[@goddessfreya]: https://github.com/goddessfreya
[@ArturKovacs]: https://github.com/ArturKovacs

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.24.0"
winit = "0.27.4"
```
## [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
@@ -66,15 +65,23 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
### 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
Winit supports compiling to the `wasm32-unknown-unknown` target with either a
`stdweb` or a `web-sys` backend for use on web browsers. However, please note
that **the `stdweb` backend is being deprecated and may be removed in a future
release of Winit**. The `web-sys` backend is also more feature complete.
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
either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
@@ -94,7 +101,19 @@ 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.
`winit` compatibility table with `ndk-glue`:
| winit | ndk-glue |
| :---: | :------------------: |
| 0.24 | `ndk-glue = "0.2.0"` |
| 0.25 | `ndk-glue = "0.3.0"` |
| 0.26 | `ndk-glue = "0.5.0"` |
| 0.27 | `ndk-glue = "0.7.0"` |
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
```toml
[[example]]
name = "request_redraw_threaded"
@@ -110,3 +129,16 @@ fn main() {
```
And run the application with `cargo apk run --example request_redraw_threaded`
#### MacOS
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,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(time::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(),
_ => (),
}
});

75
examples/drag_window.rs Normal file
View File

@@ -0,0 +1,75 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::EventLoop,
window::{Window, WindowBuilder, WindowId},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
let mut switched = false;
let mut entered_id = window_2.id();
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Switch which window is to be dragged by pressing \"x\".")
}
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
window.drag_window().unwrap()
}
WindowEvent::CursorEntered { .. } => {
entered_id = window_id;
name_windows(entered_id, switched, &window_1, &window_2)
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::X),
..
},
..
} => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
_ => (),
},
_ => (),
});
}
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
let (drag_target, other) =
if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) {
(&window_2, &window_1)
} else {
(&window_1, &window_2)
};
drag_target.set_title("drag target");
other.set_title("winit window");
}

View File

@@ -1,71 +1,108 @@
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 is_maximized = false;
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) => {
is_maximized = !is_maximized;
window.set_maximized(is_maximized);
}
(VirtualKeyCode::D, ElementState::Pressed) => {
VirtualKeyCode::D => {
decorations = !decorations;
window.set_decorations(decorations);
}
VirtualKeyCode::X => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
VirtualKeyCode::Z => {
minimized = !minimized;
window.set_minimized(minimized);
}
_ => (),
},
_ => (),
@@ -74,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};

62
examples/mouse_wheel.rs Normal file
View File

@@ -0,0 +1,62 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Mouse Wheel events")
.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.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
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;
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;
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,13 +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();
@@ -24,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");
}
@@ -38,3 +41,8 @@ fn main() {
}
});
}
#[cfg(target_arch = "wasm32")]
fn main() {
unimplemented!() // `Window` can't be sent between threads
}

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,
};
@@ -12,47 +14,20 @@ pub fn main() {
.build(&event_loop)
.unwrap();
#[cfg(feature = "web-sys")]
{
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");
}
#[cfg(feature = "stdweb")]
{
use std_web::web::INode;
use winit::platform::web::WindowExtStdweb;
let canvas = window.canvas();
let document = std_web::web::document();
let body: std_web::web::Node = document.body().expect("Get HTML body").into();
body.append_child(&canvas);
}
#[cfg(target_arch = "wasm32")]
let log_list = wasm::create_log_list(&window);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
control_flow.set_wait();
#[cfg(feature = "web-sys")]
log::debug!("{:?}", event);
#[cfg(feature = "stdweb")]
std_web::console!(log, "%s", format!("{:?}", event));
#[cfg(target_arch = "wasm32")]
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();
}
@@ -61,14 +36,55 @@ pub fn main() {
});
}
#[cfg(feature = "web-sys")]
#[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},
};
@@ -28,11 +30,12 @@ fn main() {
eprintln!(" (X) Toggle maximized");
let mut minimized = false;
let mut maximized = 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 {
@@ -59,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 => {
maximized = !maximized;
window.set_maximized(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 is `1.0`.
//! 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?
//!
@@ -69,14 +69,15 @@
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
//! global and changing it requires logging out. See [this article][windows_1] for technical
//! details.
//! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0.
//! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale
//! factor, given the use of the command line.
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
//! the specific value varies across devices.
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
//! 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
@@ -92,8 +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)
//! [`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/
@@ -163,7 +167,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
/// implementation is provided which does the rounding for you.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalPosition<P> {
pub x: P,
@@ -209,9 +213,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalPosition<P> {
fn into(self: Self) -> (X, X) {
(self.x.cast(), self.y.cast())
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
fn from(p: LogicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
@@ -221,14 +225,28 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
fn into(self: Self) -> [X; 2] {
[self.x.cast(), self.y.cast()]
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
fn from(p: LogicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(p: LogicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalPosition<P> {
pub x: P,
@@ -274,9 +292,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalPosition<P> {
fn into(self: Self) -> (X, X) {
(self.x.cast(), self.y.cast())
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
fn from(p: PhysicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
@@ -286,14 +304,28 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
fn into(self: Self) -> [X; 2] {
[self.x.cast(), self.y.cast()]
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
fn from(p: PhysicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(p: PhysicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalSize<P> {
pub width: P,
@@ -339,9 +371,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalSize<P> {
fn into(self: LogicalSize<P>) -> (X, X) {
(self.width.cast(), self.height.cast())
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
fn from(s: LogicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
@@ -351,14 +383,31 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
fn into(self: Self) -> [X; 2] {
[self.width.cast(), self.height.cast()]
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
fn from(s: LogicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(s: LogicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.height,
}
}
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalSize<P> {
pub width: P,
@@ -401,9 +450,9 @@ impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalSize<P> {
fn into(self: Self) -> (X, X) {
(self.width.cast(), self.height.cast())
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
fn from(s: PhysicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
@@ -413,9 +462,26 @@ impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
}
}
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
fn into(self: Self) -> [X; 2] {
[self.width.cast(), self.height.cast()]
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
fn from(s: PhysicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(s: PhysicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.height,
}
}
}
@@ -445,6 +511,29 @@ impl Size {
Size::Logical(size) => size.to_physical(scale_factor),
}
}
pub fn clamp<S: Into<Size>>(input: S, min: S, max: S, scale_factor: f64) -> Size {
let (input, min, max) = (
input.into().to_physical::<f64>(scale_factor),
min.into().to_physical::<f64>(scale_factor),
max.into().to_physical::<f64>(scale_factor),
);
let clamp = |input: f64, min: f64, max: f64| {
if input < min {
min
} else if input > max {
max
} else {
input
}
};
let width = clamp(input.width, min.width, max.width);
let height = clamp(input.height, min.height, max.height);
PhysicalSize::new(width, height).into()
}
}
impl<P: Pixel> From<PhysicalSize<P>> for Size {

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,32 +189,35 @@ 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.
///
/// This is irreversable - if this event is emitted, it is guaranteed to be the last event that
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
/// gets emitted. You generally want to treat this as an "do on quit" event.
LoopDestroyed,
}
@@ -131,7 +235,7 @@ impl<T: Clone> Clone for Event<'static, T> {
device_id: *device_id,
event: event.clone(),
},
NewEvents(cause) => NewEvents(cause.clone()),
NewEvents(cause) => NewEvents(*cause),
MainEventsCleared => MainEventsCleared,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
@@ -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,20 +305,26 @@ 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.
Resized(PhysicalSize<u32>),
/// The position of the window has changed. Contains the window's new position.
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Wayland:** Unsupported.
Moved(PhysicalPosition<i32>),
/// The window has been requested to close.
@@ -240,6 +352,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 +379,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,16 +474,27 @@ 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> {
fn clone(&self) -> Self {
use self::WindowEvent::*;
return match self {
Resized(size) => Resized(size.clone()),
Moved(pos) => Moved(pos.clone()),
Resized(size) => Resized(*size),
Moved(pos) => Moved(*pos),
CloseRequested => CloseRequested,
Destroyed => Destroyed,
DroppedFile(file) => DroppedFile(file.clone()),
@@ -376,8 +511,8 @@ impl Clone for WindowEvent<'static> {
input: *input,
is_synthetic: *is_synthetic,
},
ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()),
Ime(preedit_state) => Ime(preedit_state.clone()),
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
#[allow(deprecated)]
CursorMoved {
device_id,
@@ -437,10 +572,11 @@ impl Clone for WindowEvent<'static> {
value: *value,
},
Touch(touch) => Touch(*touch),
ThemeChanged(theme) => ThemeChanged(theme.clone()),
ThemeChanged(theme) => ThemeChanged(*theme),
ScaleFactorChanged { .. } => {
unreachable!("Static event can't be about scale factor changing")
}
Occluded(occluded) => Occluded(*occluded),
};
}
}
@@ -468,6 +604,7 @@ impl<'a> WindowEvent<'a> {
is_synthetic,
}),
ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)),
Ime(event) => Some(Ime(event)),
#[allow(deprecated)]
CursorMoved {
device_id,
@@ -525,6 +662,7 @@ impl<'a> WindowEvent<'a> {
Touch(touch) => Some(Touch(touch)),
ThemeChanged(theme) => Some(ThemeChanged(theme)),
ScaleFactorChanged { .. } => None,
Occluded(occluded) => Some(Occluded(occluded)),
}
}
}
@@ -538,12 +676,16 @@ impl<'a> WindowEvent<'a> {
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
/// value of this function is that it will always be equal to itself and to future values returned
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
@@ -563,7 +705,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.
///
@@ -576,7 +718,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 {
@@ -623,6 +765,75 @@ 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, 0)))
/// // Press "E" key
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
/// 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, 1)))
/// // Press "B" key
/// Ime::Preedit("a b", Some((3, 3)))
/// // Press left arrow key
/// Ime::Preedit("a b", Some((1, 1)))
/// // Press space key
/// Ime::Preedit("啊b", Some((3, 3)))
/// // Press space key
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
/// 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. When `String` is an empty string
/// this indicates that preedit was cleared.
///
/// The cursor position is byte-wise indexed.
Preedit(String, Option<(usize, usize)>),
/// Notifies when text should be inserted into the editor widget.
///
/// Right before this event winit will send empty [`Self::Preedit`] event.
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 should
/// 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))]
@@ -635,18 +846,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)]
@@ -700,8 +911,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 {
@@ -755,15 +967,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>),
}
@@ -998,9 +1218,9 @@ bitflags! {
// left and right modifiers are currently commented out, but we should be able to support
// them in a future release
/// The "shift" key.
const SHIFT = 0b100 << 0;
// const LSHIFT = 0b010 << 0;
// const RSHIFT = 0b001 << 0;
const SHIFT = 0b100;
// const LSHIFT = 0b010;
// const RSHIFT = 0b001;
/// The "control" key.
const CTRL = 0b100 << 3;
// const LCTRL = 0b010 << 3;

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,39 @@ 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 / 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",
target_os = "windows"
))]
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 +360,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 +378,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 +392,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(broken_intra_doc_links)]
#![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;
@@ -146,8 +148,6 @@ extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(all(target_arch = "wasm32", feature = "std_web"))]
extern crate std_web as stdweb;
pub mod dpi;
#[macro_use]

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,21 +4,21 @@ use std::os::raw::c_void;
use crate::{
dpi::LogicalSize,
event_loop::EventLoopWindowTarget,
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
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.
@@ -73,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,
@@ -89,19 +89,15 @@ 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 {
/// Sets the activation policy for the window being built.
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool)
-> WindowBuilder;
@@ -122,12 +118,6 @@ pub trait WindowBuilderExtMacOS {
}
impl WindowBuilderExtMacOS for WindowBuilder {
#[inline]
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
self.platform_specific.activation_policy = activation_policy;
self
}
#[inline]
fn with_movable_by_window_background(
mut self,
@@ -169,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
}
@@ -186,7 +176,67 @@ impl WindowBuilderExtMacOS for WindowBuilder {
}
}
/// Additional methods on `MonitorHandle` that are specific to MacOS.
pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application.
///
/// 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 control whether a default menubar menu is created.
///
/// Menu creation is enabled by default.
///
/// # 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> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy;
self
}
#[inline]
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.
pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
@@ -205,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);
@@ -215,14 +265,10 @@ pub trait EventLoopWindowTargetExtMacOS {
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_application(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
self.p.hide_application()
}
fn hide_other_applications(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap();
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hideOtherApplications: 0] }
self.p.hide_other_applications()
}
}

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,20 +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>;
/// Sets the color theme of the client side window decorations on wayland
/// 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 set_wayland_theme<T: Theme>(&self, theme: T);
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;
}
@@ -283,7 +257,6 @@ impl WindowExtUnix for Window {
}
#[inline]
#[doc(hidden)]
#[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
@@ -325,11 +298,12 @@ impl WindowExtUnix for Window {
#[inline]
#[cfg(feature = "wayland")]
fn set_wayland_theme<T: Theme>(&self, theme: T) {
fn wayland_set_csd_theme(&self, theme: Theme) {
#[allow(clippy::single_match)]
match self.window {
LinuxWindow::Wayland(ref w) => w.set_theme(theme),
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
#[cfg(feature = "x11")]
_ => {}
_ => (),
}
}
@@ -339,39 +313,74 @@ impl WindowExtUnix for Window {
}
}
/// 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 {
@@ -393,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
}
@@ -420,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 {
@@ -433,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.
@@ -454,78 +462,3 @@ impl MonitorHandleExtUnix for MonitorHandle {
self.inner.native_identifier()
}
}
/// A theme for a Wayland's client side decorations.
#[cfg(feature = "wayland")]
pub trait Theme: Send + 'static {
/// Title bar color.
fn element_color(&self, element: Element, window_active: bool) -> ARGBColor;
/// Color for a given button part.
fn button_color(
&self,
button: Button,
state: ButtonState,
foreground: bool,
window_active: bool,
) -> ARGBColor;
/// Font name and the size for the title bar.
///
/// By default the font is `sans-serif` at the size of 17.
///
/// Returning `None` means that title won't be drawn.
fn font(&self) -> Option<(String, f32)> {
// Not having any title isn't something desirable for the users, so setting it to
// something generic.
Some((String::from("sans-serif"), 17.))
}
}
/// A button on Wayland's client side decorations.
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Button {
/// Button that maximizes the window.
Maximize,
/// Button that minimizes the window.
Minimize,
/// Button that closes the window.
Close,
}
/// A button state of the button on Wayland's client side decorations.
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ButtonState {
/// Button is being hovered over by pointer.
Hovered,
/// Button is not being hovered over by pointer.
Idle,
/// Button is disabled.
Disabled,
}
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Element {
/// Bar itself.
Bar,
/// Separator between window and title bar.
Separator,
/// Title bar text.
Text,
}
#[cfg(feature = "wayland")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ARGBColor {
pub a: u8,
pub r: u8,
pub g: u8,
pub b: u8,
}

View File

@@ -1,28 +1,18 @@
#![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 `WindowExtStdweb` or
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
//! your own canvas.
//! 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;
#[cfg(feature = "stdweb")]
use stdweb::web::html_element::CanvasElement;
#[cfg(feature = "stdweb")]
pub trait WindowExtStdweb {
fn canvas(&self) -> CanvasElement;
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "web-sys")]
use web_sys::HtmlCanvasElement;
#[cfg(feature = "web-sys")]
pub trait WindowExtWebSys {
fn canvas(&self) -> HtmlCanvasElement;
@@ -30,30 +20,72 @@ pub trait WindowExtWebSys {
fn is_dark_mode(&self) -> bool;
}
#[cfg(feature = "stdweb")]
pub trait WindowBuilderExtStdweb {
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
}
#[cfg(feature = "stdweb")]
impl WindowBuilderExtStdweb for WindowBuilder {
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self
}
}
#[cfg(feature = "web-sys")]
pub trait WindowBuilderExtWebSys {
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// 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;
}
#[cfg(feature = "web-sys")]
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
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,99 +1,169 @@
#![cfg(target_os = "windows")]
use std::os::raw::c_void;
use std::path::Path;
use libc;
use winapi::shared::minwindef::WORD;
use winapi::shared::windef::HWND;
use 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, 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 libc::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 libc::c_void;
fn hwnd(&self) -> HWND;
/// Enables or disables mouse and keyboard input to the specified window.
///
/// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable
/// the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated.
///
/// If a child window is disabled, it is ignored when the system tries to determine which
/// window should receive mouse messages.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
fn set_enable(&self, enabled: bool);
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
/// Returns the current window theme.
fn theme(&self) -> Theme;
/// Whether to show or hide the window icon in the taskbar.
fn set_skip_taskbar(&self, skip: bool);
/// Shows or hides the background drop shadow for undecorated windows.
///
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn set_undecorated_shadow(&self, shadow: bool);
}
impl WindowExtWindows for Window {
#[inline]
fn hinstance(&self) -> *mut libc::c_void {
self.window.hinstance() as *mut _
fn hinstance(&self) -> HINSTANCE {
self.window.hinstance()
}
#[inline]
fn hwnd(&self) -> *mut libc::c_void {
self.window.hwnd() as *mut _
fn hwnd(&self) -> HWND {
self.window.hwnd()
}
#[inline]
fn set_enable(&self, enabled: bool) {
self.window.set_enable(enabled)
}
#[inline]
@@ -105,13 +175,51 @@ 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)
}
#[inline]
fn set_undecorated_shadow(&self, shadow: bool) {
self.window.set_undecorated_shadow(shadow)
}
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
pub trait WindowBuilderExtWindows {
/// Sets a parent to the window to be created.
///
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box.
///
/// From MSDN:
/// - An owned window is always above its owner in the z-order.
/// - The system automatically destroys an owned window when its owner is destroyed.
/// - An owned window is hidden when its owner is minimized.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
/// Sets a menu on the window to be created.
///
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
///
/// 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.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
@@ -123,17 +231,38 @@ pub trait WindowBuilderExtWindows {
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
/// See https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks for more information.
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
/// Forces a theme or uses the system settings if `None` was provided.
fn with_theme(self, theme: Option<Theme>) -> WindowBuilder;
/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
/// Shows or hides the background drop shadow for undecorated windows.
///
/// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder;
}
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Some(parent);
self.platform_specific.parent = Parent::ChildOf(parent);
self
}
#[inline]
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
self.platform_specific.parent = Parent::OwnedBy(parent);
self
}
#[inline]
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
self.platform_specific.menu = Some(menu);
self
}
@@ -160,6 +289,18 @@ 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
}
#[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
self.platform_specific.decoration_shadow = shadow;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Windows.
@@ -168,7 +309,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 {
@@ -178,8 +319,8 @@ impl MonitorHandleExtWindows for MonitorHandle {
}
#[inline]
fn hmonitor(&self) -> *mut c_void {
self.inner.hmonitor() as *mut _
fn hmonitor(&self) -> HMONITOR {
self.inner.hmonitor()
}
}
@@ -217,7 +358,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 {
@@ -229,7 +370,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,67 +1,267 @@
#![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, MotionAction},
looper::{ForeignLooper, Poll, ThreadLooper},
};
use ndk_glue::{Event, Rect};
use std::{
collections::VecDeque,
sync::{Arc, Mutex, RwLock},
sync::{mpsc, RwLock},
time::{Duration, Instant},
};
lazy_static! {
static ref CONFIG: RwLock<Configuration> = RwLock::new(Configuration::new());
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,
}
enum EventSource {
Callback,
InputQueue,
User,
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 { data, .. } => match data as usize {
0 => Some(EventSource::Callback),
1 => Some(EventSource::InputQueue),
Poll::Event { ident, .. } => match ident {
ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback),
ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue),
_ => unreachable!(),
},
Poll::Timeout => None,
Poll::Wake => Some(EventSource::User),
Poll::Wake => Some(
INTERNAL_EVENT
.write()
.unwrap()
.take()
.map_or(EventSource::User, EventSource::Internal),
),
Poll::Callback => unreachable!(),
}
}
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 {
@@ -69,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,
}
}
@@ -82,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),
{
@@ -106,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();
@@ -174,8 +397,9 @@ 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);
let device_id = event::DeviceId(DeviceId);
match &event {
@@ -191,7 +415,10 @@ impl<T: 'static> EventLoop<T> {
MotionAction::Cancel => {
Some(event::TouchPhase::Cancelled)
}
_ => None, // TODO mouse events
_ => {
handled = false;
None // TODO mouse events
}
};
if let Some(phase) = phase {
let pointers: Box<
@@ -235,16 +462,45 @@ impl<T: 'static> EventLoop<T> {
}
}
}
InputEvent::KeyEvent(_) => {} // TODO
InputEvent::KeyEvent(key) => {
let state = match key.action() {
KeyAction::Down => event::ElementState::Pressed,
KeyAction::Up => event::ElementState::Released,
_ => event::ElementState::Released,
};
#[allow(deprecated)]
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::KeyboardInput {
device_id,
input: event::KeyboardInput {
scancode: key.scan_code() as u32,
state,
virtual_keycode: ndk_keycode_to_virtualkeycode(
key.key_code(),
),
modifiers: event::ModifiersState::default(),
},
is_synthetic: false,
},
};
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event
);
}
};
input_queue.finish_event(event, true);
input_queue.finish_event(event, handled);
}
}
}
}
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(),
@@ -253,6 +509,9 @@ impl<T: 'static> EventLoop<T> {
);
}
}
Some(EventSource::Internal(internal)) => match internal {
InternalEvent::RedrawRequested => redraw = true,
},
None => {}
}
@@ -285,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))
@@ -295,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(
@@ -342,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(())
}
@@ -364,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(),
}
}
@@ -386,22 +647,38 @@ 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)]
pub struct WindowId;
impl WindowId {
pub fn dummy() -> Self {
pub const fn dummy() -> Self {
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;
impl DeviceId {
pub fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}
@@ -412,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,
@@ -448,7 +725,8 @@ impl Window {
}
pub fn request_redraw(&self) {
// TODO
*INTERNAL_EVENT.write().unwrap() = Some(InternalEvent::RedrawRequested);
ForeignLooper::for_thread().unwrap().wake();
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
@@ -483,12 +761,24 @@ 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) {}
pub fn is_maximized(&self) -> bool {
false
}
pub fn set_fullscreen(&self, _monitor: Option<window::Fullscreen>) {
warn!("Cannot set fullscreen on Android");
}
@@ -499,12 +789,20 @@ 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>) {}
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
@@ -515,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(),
))
@@ -523,15 +821,28 @@ impl Window {
pub fn set_cursor_visible(&self, _: bool) {}
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() {
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
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 {
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.");
};
let mut handle = raw_window_handle::android::AndroidHandle::empty();
handle.a_native_window = a_native_window;
raw_window_handle::RawWindowHandle::Android(handle)
}
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
}
pub fn config(&self) -> Configuration {
@@ -585,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()
})
}
}
@@ -606,7 +920,7 @@ impl MonitorHandle {
pub struct VideoMode {
size: (u32, u32),
bit_depth: u16,
refresh_rate: u16,
refresh_rate_millihertz: u32,
monitor: MonitorHandle,
}
@@ -619,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,9 @@ use std::{
sync::mpsc::{self, Receiver, Sender},
};
use objc::runtime::Object;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use crate::{
dpi::LogicalSize,
event::Event,
@@ -63,14 +66,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");
@@ -104,7 +114,7 @@ impl<T: 'static> EventLoop<T> {
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
let application: *mut Object = msg_send![class!(UIApplication), sharedApplication];
assert_eq!(
application,
ptr::null_mut(),

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`")
}
}
@@ -375,7 +374,7 @@ pub trait NSStringRust: Sized {
impl NSStringRust for id {
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
msg_send![self, initWithUTF8String: c_string as id]
msg_send![self, initWithUTF8String: c_string]
}
unsafe fn stringByAppendingString_(self, other: id) -> id {

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},
};
@@ -91,7 +95,7 @@ pub struct DeviceId {
}
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId {
uiscreen: std::ptr::null_mut(),
}
@@ -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 _: id = 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];
@@ -262,7 +262,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _),
scale_factor,
scale_factor as f64,
)
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
@@ -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::{ios::IOSHandle, RawWindowHandle};
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()))
}
@@ -182,6 +193,14 @@ impl Inner {
debug!("`Window::set_cursor_visible` is ignored on iOS")
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
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")
}
@@ -190,12 +209,18 @@ impl Inner {
warn!("`Window::set_maximized` is ignored on iOS")
}
pub fn is_maximized(&self) -> bool {
warn!("`Window::is_maximized` is ignored on iOS");
false
}
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe {
let uiscreen = match monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
let _: () =
msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
uiscreen
}
Some(Fullscreen::Borderless(monitor)) => monitor
@@ -210,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
];
@@ -250,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")
}
@@ -262,6 +292,14 @@ 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")
}
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
warn!("`Window::request_user_attention` is ignored on iOS")
}
@@ -294,13 +332,15 @@ impl Inner {
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let handle = IOSHandle {
ui_window: self.window as _,
ui_view: self.view as _,
ui_view_controller: self.view_controller as _,
..IOSHandle::empty()
};
RawWindowHandle::IOS(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())
}
}
@@ -340,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 {
@@ -371,8 +411,8 @@ impl Window {
let frame = match window_attributes.inner_size {
Some(dim) => {
let scale_factor = msg_send![screen, scale];
let size = dim.to_logical::<f64>(scale_factor);
let scale_factor: CGFloat = msg_send![screen, scale];
let size = dim.to_logical::<f64>(scale_factor as f64);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
@@ -384,11 +424,11 @@ 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];
let layer_class: id = msg_send![view_class, layerClass];
let view_class: *const Class = msg_send![view, class];
let layer_class: *const Class = msg_send![view_class, layerClass];
let is_metal: BOOL =
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
@@ -417,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];
@@ -467,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];
}
}
@@ -488,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
];
@@ -498,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
];
@@ -508,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
];
@@ -519,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];
@@ -534,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];
@@ -559,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!(
@@ -598,13 +638,27 @@ pub struct WindowId {
}
impl WindowId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
WindowId {
window: std::ptr::null_mut(),
}
}
}
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 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());
pub const unsafe fn dummy() -> Self {
Self(0)
}
}
@@ -158,7 +193,7 @@ pub enum DeviceId {
}
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
#[cfg(feature = "wayland")]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
@@ -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]
@@ -358,6 +413,16 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
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)
@@ -373,6 +438,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
}
#[inline]
pub fn is_maximized(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_maximized())
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
@@ -393,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),
Window::X(ref w) => w.set_always_on_top(_always_on_top),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
@@ -407,9 +482,9 @@ impl Window {
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
match self {
#[cfg(feature = "x11")]
&Window::X(ref w) => w.set_window_icon(_window_icon),
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
@@ -419,12 +494,25 @@ impl Window {
}
#[inline]
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
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.request_user_attention(_request_type),
Window::X(ref w) => w.focus_window(),
#[cfg(feature = "wayland")]
_ => (),
Window::Wayland(_) => (),
}
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
match self {
#[cfg(feature = "x11")]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(feature = "wayland")]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
}
@@ -437,14 +525,14 @@ impl Window {
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => {
Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(RootMonitorHandle {
inner: current_monitor,
})
}
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => {
Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(RootMonitorHandle {
inner: current_monitor,
@@ -457,13 +545,13 @@ impl Window {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => window
Window::X(ref window) => window
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => window
Window::Wayland(ref window) => window
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
@@ -475,27 +563,33 @@ impl Window {
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
match self {
#[cfg(feature = "x11")]
&Window::X(ref window) => {
Window::X(ref window) => {
let primary_monitor = MonitorHandle::X(window.primary_monitor());
Some(RootMonitorHandle {
inner: primary_monitor,
})
}
#[cfg(feature = "wayland")]
&Window::Wayland(ref window) => window.primary_monitor(),
Window::Wayland(ref window) => window.primary_monitor(),
}
}
#[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,
@@ -503,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();
@@ -521,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);
}
@@ -531,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>),
}
@@ -550,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" => {
@@ -598,34 +716,19 @@ impl<T: 'static> EventLoop<T> {
#[cfg(not(feature = "x11"))]
let x11_err = "backend disabled";
let err_string = format!(
panic!(
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
wayland_err, x11_err,
);
panic!(err_string);
}
#[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()),
@@ -638,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),
{
@@ -653,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())
}
}
@@ -714,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>(
@@ -724,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

@@ -13,6 +13,7 @@ use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::{Environment, SimpleGlobal};
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
@@ -23,18 +24,27 @@ 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();
Self { cursor_grab }
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
pointer_constraints,
xdg_activation,
}
}
pub fn cursor_grab(&self) -> bool {
self.cursor_grab
pub fn pointer_constraints(&self) -> bool {
self.pointer_constraints
}
pub fn xdg_activation(&self) -> bool {
self.xdg_activation
}
}
@@ -50,6 +60,7 @@ sctk::environment!(WinitEnv,
ZwpRelativePointerManagerV1 => relative_pointer_manager,
ZwpPointerConstraintsV1 => pointer_constraints,
ZwpTextInputManagerV3 => text_input_manager,
XdgActivationV1 => xdg_activation,
],
multis = [
WlSeat => seats,
@@ -78,6 +89,8 @@ pub struct WinitEnv {
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
xdg_activation: SimpleGlobal<XdgActivationV1>,
}
impl WinitEnv {
@@ -109,6 +122,9 @@ impl WinitEnv {
// IME handling.
let text_input_manager = SimpleGlobal::new();
// Surface activation.
let xdg_activation = SimpleGlobal::new();
Self {
seats,
outputs,
@@ -120,6 +136,7 @@ impl WinitEnv {
relative_pointer_manager,
pointer_constraints,
text_input_manager,
xdg_activation,
}
}
}

View File

@@ -1,10 +1,14 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::io::Result as IOResult;
use std::mem;
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;
@@ -18,11 +22,12 @@ use sctk::WaylandSource;
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use super::env::{WindowingFeatures, WinitEnv};
use super::output::OutputManager;
use super::seat::SeatManager;
use super::window::shim::{self, WindowUpdate};
use super::window::shim::{self, WindowCompositorUpdate, WindowUserRequest};
use super::{DeviceId, WindowId};
mod proxy;
@@ -30,9 +35,10 @@ 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> {
/// Wayland display.
@@ -42,7 +48,7 @@ pub struct EventLoopWindowTarget<T> {
pub env: Environment<WinitEnv>,
/// Event loop handle.
pub event_loop_handle: calloop::LoopHandle<WinitState>,
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
/// Output manager.
pub output_manager: OutputManager,
@@ -50,8 +56,8 @@ pub struct EventLoopWindowTarget<T> {
/// State that we share across callbacks.
pub state: RefCell<WinitState>,
/// Wayland source.
pub wayland_source: Rc<calloop::Source<WaylandSource>>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// A proxy to wake up event loop.
pub event_loop_awakener: calloop::ping::Ping,
@@ -61,16 +67,27 @@ 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> {
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// Event loop.
event_loop: calloop::EventLoop<WinitState>,
event_loop: calloop::EventLoop<'static, WinitState>,
/// Wayland display.
display: Display,
@@ -81,9 +98,6 @@ pub struct EventLoop<T: 'static> {
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
/// Wayland source of events.
wayland_source: Rc<calloop::Source<WaylandSource>>,
/// Window target.
window_target: RootEventLoopWindowTarget<T>,
@@ -102,7 +116,7 @@ impl<T: 'static> EventLoop<T> {
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
// Create event loop.
let event_loop = calloop::EventLoop::<WinitState>::new()?;
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
// Build windowing features.
let windowing_features = WindowingFeatures::new(&env);
@@ -116,8 +130,22 @@ impl<T: 'static> EventLoop<T> {
let output_manager = OutputManager::new(&env);
// A source of events that we plug into our event loop.
let wayland_source = WaylandSource::new(event_queue).quick_insert(event_loop.handle())?;
let wayland_source = Rc::new(wayland_source);
let wayland_source = WaylandSource::new(event_queue);
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state, |event, object, _| {
panic!(
"[calloop] Encountered an orphan event: {}@{} : {}",
event.interface,
object.as_ref().id(),
event.name
);
})
});
let _wayland_source_dispatcher = event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
// A source of user events.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
@@ -137,17 +165,19 @@ impl<T: 'static> EventLoop<T> {
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
// Handler of window requests.
event_loop.handle().insert_source(
event_loop_awakener_source,
move |_, _, winit_state| {
shim::handle_window_requests(winit_state);
},
)?;
event_loop
.handle()
.insert_source(event_loop_awakener_source, move |_, _, state| {
// Drain events here as well to account for application doing batch event processing
// on RedrawEventsCleared.
shim::handle_window_requests(state);
})?;
let event_loop_handle = event_loop.handle();
let window_map = HashMap::new();
let event_sink = EventSink::new();
let window_updates = HashMap::new();
let window_user_requests = HashMap::new();
let window_compositor_updates = HashMap::new();
// Create event loop window target.
let event_loop_window_target = EventLoopWindowTarget {
@@ -156,12 +186,13 @@ impl<T: 'static> EventLoop<T> {
state: RefCell::new(WinitState {
window_map,
event_sink,
window_updates,
window_user_requests,
window_compositor_updates,
}),
event_loop_handle,
output_manager,
event_loop_awakener,
wayland_source: wayland_source.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
windowing_features,
theme_manager,
_marker: std::marker::PhantomData,
@@ -172,11 +203,11 @@ impl<T: 'static> EventLoop<T> {
event_loop,
display,
pending_user_events,
wayland_source,
wayland_dispatcher,
_seat_manager: seat_manager,
user_events_sender,
window_target: RootEventLoopWindowTarget {
p: crate::platform_impl::EventLoopWindowTarget::Wayland(event_loop_window_target),
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
_marker: std::marker::PhantomData,
},
};
@@ -188,19 +219,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(
@@ -209,7 +236,12 @@ impl<T: 'static> EventLoop<T> {
&mut control_flow,
);
let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
// 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_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new();
let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = Vec::new();
let mut event_sink_back_buffer = Vec::new();
// NOTE We break on errors from dispatches, since if we've got protocol error
@@ -217,7 +249,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(..) {
@@ -229,25 +362,26 @@ impl<T: 'static> EventLoop<T> {
);
}
// Process 'new' pending updates.
// Process 'new' pending updates from compositor.
self.with_state(|state| {
window_updates.clear();
window_updates.extend(
window_compositor_updates.clear();
window_compositor_updates.extend(
state
.window_updates
.window_compositor_updates
.iter_mut()
.map(|(wid, window_update)| (*wid, window_update.take())),
.map(|(wid, window_update)| (*wid, mem::take(window_update))),
);
});
for (window_id, window_update) in window_updates.iter_mut() {
if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) {
for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() {
if let Some(scale_factor) = window_compositor_update.scale_factor.map(|f| f as f64)
{
let mut physical_size = self.with_state(|state| {
let window_handle = state.window_map.get(&window_id).unwrap();
let window_handle = state.window_map.get(window_id).unwrap();
let mut size = window_handle.size.lock().unwrap();
// Update the new logical size if it was changed.
let window_size = window_update.size.unwrap_or(*size);
let window_size = window_compositor_update.size.unwrap_or(*size);
*size = window_size;
window_size.to_physical(scale_factor)
@@ -255,9 +389,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,
@@ -271,26 +403,27 @@ impl<T: 'static> EventLoop<T> {
// We don't update size on a window handle since we'll do that later
// when handling size update.
let new_logical_size = physical_size.to_logical(scale_factor);
window_update.size = Some(new_logical_size);
window_compositor_update.size = Some(new_logical_size);
}
if let Some(size) = window_update.size.take() {
if let Some(size) = window_compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let window_handle = state.window_map.get_mut(&window_id).unwrap();
let window_handle = state.window_map.get_mut(window_id).unwrap();
let mut window_size = window_handle.size.lock().unwrap();
// Always issue resize event on scale factor change.
let physical_size =
if window_update.scale_factor.is_none() && *window_size == size {
// The size hasn't changed, don't inform downstream about that.
None
} else {
*window_size = size;
let scale_factor =
sctk::get_surface_scale_factor(&window_handle.window.surface());
let physical_size = size.to_physical(scale_factor as f64);
Some(physical_size)
};
let physical_size = if window_compositor_update.scale_factor.is_none()
&& *window_size == size
{
// The size hasn't changed, don't inform downstream about that.
None
} else {
*window_size = size;
let scale_factor =
sctk::get_surface_scale_factor(window_handle.window.surface());
let physical_size = size.to_physical(scale_factor as f64);
Some(physical_size)
};
// We still perform all of those resize related logic even if the size
// hasn't changed, since GNOME relies on `set_geometry` calls after
@@ -299,7 +432,11 @@ impl<T: 'static> EventLoop<T> {
window_handle.window.refresh();
// Mark that refresh isn't required, since we've done it right now.
window_update.refresh_frame = false;
state
.window_user_requests
.get_mut(window_id)
.unwrap()
.refresh_frame = false;
physical_size
});
@@ -307,9 +444,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,
@@ -319,12 +454,11 @@ impl<T: 'static> EventLoop<T> {
}
}
if window_update.close_window {
// If the close is requested, send it here.
if window_compositor_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,
@@ -358,25 +492,42 @@ impl<T: 'static> EventLoop<T> {
&mut callback,
);
// Apply user requests, so every event required resize and latter surface commit will
// be applied right before drawing. This will also ensure that every `RedrawRequested`
// event will be delivered in time.
self.with_state(|state| {
shim::handle_window_requests(state);
});
// Process 'new' pending updates from compositor.
self.with_state(|state| {
window_user_requests.clear();
window_user_requests.extend(
state
.window_user_requests
.iter_mut()
.map(|(wid, window_request)| (*wid, mem::take(window_request))),
);
});
// Handle RedrawRequested events.
for (window_id, window_update) in window_updates.iter() {
for (window_id, mut window_request) in window_user_requests.iter() {
// Handle refresh of the frame.
if window_update.refresh_frame {
if window_request.refresh_frame {
self.with_state(|state| {
let window_handle = state.window_map.get_mut(&window_id).unwrap();
let window_handle = state.window_map.get_mut(window_id).unwrap();
window_handle.window.refresh();
if !window_update.redraw_requested {
window_handle.window.surface().commit();
}
});
// In general refreshing the frame requires surface commit, those force user
// to redraw.
window_request.redraw_requested = true;
}
// Handle redraw request.
if window_update.redraw_requested {
if window_request.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,
@@ -391,109 +542,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 handle = self.event_loop.handle();
let source = self.wayland_source.clone();
let dispatched = handle.with_source(&source, |wayland_source| {
let queue = wayland_source.queue();
self.with_state(|state| {
queue.dispatch_pending(state, |_, _, _| unimplemented!())
})
});
if let Ok(dispatched) = dispatched {
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]
@@ -508,9 +560,7 @@ impl<T: 'static> EventLoop<T> {
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
let state = match &mut self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => {
window_target.state.get_mut()
}
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
@@ -518,18 +568,15 @@ impl<T: 'static> EventLoop<T> {
f(state)
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(
&mut self,
timeout: D,
) -> std::io::Result<()> {
let mut state = match &mut self.window_target.p {
crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => {
window_target.state.get_mut()
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
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

@@ -3,7 +3,9 @@
use std::collections::HashMap;
use super::EventSink;
use crate::platform_impl::wayland::window::shim::{WindowHandle, WindowUpdate};
use crate::platform_impl::wayland::window::shim::{
WindowCompositorUpdate, WindowHandle, WindowUserRequest,
};
use crate::platform_impl::wayland::WindowId;
/// Wrapper to carry winit's state.
@@ -12,10 +14,14 @@ pub struct WinitState {
/// event loop and forwarded downstream afterwards.
pub event_sink: EventSink,
/// Window updates comming from the user requests. Those are separatelly dispatched right after
/// `MainEventsCleared`.
pub window_user_requests: HashMap<WindowId, WindowUserRequest>,
/// Window updates, which are coming from SCTK or the compositor, which require
/// calling back to the winit's downstream. They are handled right in the event loop,
/// unlike the ones coming from buffers on the `WindowHandle`'s.
pub window_updates: HashMap<WindowId, WindowUpdate>,
pub window_compositor_updates: HashMap<WindowId, WindowCompositorUpdate>,
/// Window map containing all SCTK windows. Since those windows aren't allowed
/// to be sent to other threads, they live on the event loop's thread

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;
@@ -22,21 +23,12 @@ mod window;
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize);
impl WindowId {
pub 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

@@ -112,7 +112,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))
}
}
@@ -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,9 +7,9 @@ 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, Source};
use sctk::reexports::calloop::LoopHandle;
use sctk::seat::keyboard::{self, RepeatSource};
use sctk::seat::keyboard;
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
@@ -20,39 +20,28 @@ mod keymap;
pub(crate) struct Keyboard {
pub keyboard: WlKeyboard,
/// The source for repeat keys.
pub repeat_source: Option<Source<RepeatSource>>,
/// LoopHandle to drop `RepeatSource`, when dropping the keyboard.
pub loop_handle: LoopHandle<WinitState>,
}
impl Keyboard {
pub fn new(
seat: &Attached<WlSeat>,
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, WinitState>,
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,
seat,
None,
keyboard::RepeatKind::System,
move |event, _, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_keyboard(event, &mut inner, winit_state);
},
);
)
.ok()?;
let (keyboard, repeat_source) = keyboard_data.ok()?;
Some(Self {
keyboard,
loop_handle,
repeat_source: Some(repeat_source),
})
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_source) = self.repeat_source.take() {
self.loop_handle.remove(repeat_source);
}
}
}

View File

@@ -37,7 +37,7 @@ pub struct SeatManager {
impl SeatManager {
pub fn new(
env: &Environment<WinitEnv>,
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, WinitState>,
theme_manager: ThemeManager,
) -> Self {
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
@@ -63,7 +63,7 @@ impl SeatManager {
}
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
inner.process_seat_update(&seat, &seat_data);
inner.process_seat_update(&seat, seat_data);
});
Self {
@@ -78,7 +78,7 @@ struct SeatManagerInner {
seats: Vec<SeatInfo>,
/// Loop handle.
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, WinitState>,
/// Relative pointer manager.
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
@@ -99,7 +99,7 @@ impl SeatManagerInner {
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
loop_handle: LoopHandle<WinitState>,
loop_handle: LoopHandle<'static, WinitState>,
) -> Self {
Self {
seats: Vec::new(),
@@ -127,7 +127,7 @@ impl SeatManagerInner {
if seat_data.has_pointer && !seat_data.defunct {
if seat_info.pointer.is_none() {
seat_info.pointer = Some(Pointers::new(
&seat,
seat,
&self.theme_manager,
&self.relative_pointer_manager,
&self.pointer_constraints,
@@ -142,7 +142,7 @@ impl SeatManagerInner {
if seat_data.has_keyboard && !seat_data.defunct {
if seat_info.keyboard.is_none() {
seat_info.keyboard = Keyboard::new(
&seat,
seat,
self.loop_handle.clone(),
seat_info.modifiers_state.clone(),
);
@@ -154,7 +154,7 @@ impl SeatManagerInner {
// Handle touch.
if seat_data.has_touch && !seat_data.defunct {
if seat_info.touch.is_none() {
seat_info.touch = Some(Touch::new(&seat));
seat_info.touch = Some(Touch::new(seat));
}
} else {
seat_info.touch = None;
@@ -165,7 +165,7 @@ impl SeatManagerInner {
if seat_data.defunct {
seat_info.text_input = None;
} else if seat_info.text_input.is_none() {
seat_info.text_input = Some(TextInput::new(&seat, &text_input_manager));
seat_info.text_input = Some(TextInput::new(seat, text_input_manager));
}
}
}

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

@@ -4,6 +4,7 @@ use std::cell::RefCell;
use std::rc::Rc;
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
use sctk::seat::pointer::ThemedPointer;
@@ -28,6 +29,7 @@ pub(super) fn handle_pointer(
event: PointerEvent,
pointer_data: &Rc<RefCell<PointerData>>,
winit_state: &mut WinitState,
seat: WlSeat,
) {
let event_sink = &mut winit_state.event_sink;
let mut pointer_data = pointer_data.borrow_mut();
@@ -40,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) {
@@ -57,8 +60,11 @@ 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);
@@ -99,8 +105,11 @@ 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);
@@ -125,7 +134,7 @@ pub(super) fn handle_pointer(
let window_id = wayland::make_wid(surface);
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
event_sink.push_window_event(
@@ -182,20 +191,20 @@ pub(super) fn handle_pointer(
None => return,
};
let window_id = wayland::make_wid(&surface);
let window_id = wayland::make_wid(surface);
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// 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!(),
}
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor);
event_sink.push_window_event(
@@ -212,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!(),
}
@@ -233,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!(),
}
@@ -258,7 +267,7 @@ pub(super) fn handle_pointer(
Some(surface) => surface,
None => return,
};
let window_id = wayland::make_wid(&surface);
let window_id = wayland::make_wid(surface);
let window_event = if let Some((x, y)) = axis_discrete_buffer {
WindowEvent::MouseWheel {
@@ -270,7 +279,7 @@ pub(super) fn handle_pointer(
modifiers: *pointer_data.modifiers_state.borrow(),
}
} else if let Some((x, y)) = axis_buffer {
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
WindowEvent::MouseWheel {
@@ -293,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,11 +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::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;
@@ -33,8 +36,19 @@ 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,
}
impl PartialEq for WinitPointer {
@@ -54,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;
}
};
@@ -66,7 +82,7 @@ impl WinitPointer {
CursorIcon::Copy => &["copy"],
CursorIcon::Crosshair => &["crosshair"],
CursorIcon::Default => &["left_ptr"],
CursorIcon::Hand => &["hand"],
CursorIcon::Hand => &["hand2", "hand1"],
CursorIcon::Help => &["question_arrow"],
CursorIcon::Move => &["move"],
CursorIcon::Grab => &["openhand", "grab"],
@@ -89,8 +105,8 @@ impl WinitPointer {
CursorIcon::WResize => &["left_side"],
CursorIcon::EwResize => &["h_double_arrow"],
CursorIcon::NsResize => &["v_double_arrow"],
CursorIcon::NwseResize => &["bd_double_arrow", "size_bdiag"],
CursorIcon::NeswResize => &["fd_double_arrow", "size_fdiag"],
CursorIcon::NwseResize => &["bd_double_arrow", "size_fdiag"],
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
CursorIcon::Text => &["text", "xterm"],
@@ -102,12 +118,13 @@ 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() {
break;
return;
}
}
warn!("Failed to set cursor to {:?}", cursor_icon);
}
/// Confine the pointer to a surface.
@@ -124,8 +141,8 @@ impl WinitPointer {
};
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
&pointer_constraints,
&surface,
pointer_constraints,
surface,
&*self.pointer,
));
}
@@ -144,6 +161,58 @@ impl WinitPointer {
confined_pointer.destroy();
}
}
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());
}
}
/// A pointer wrapper for easy releasing and managing pointers.
@@ -156,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 {
@@ -167,31 +239,42 @@ 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,
move |event, pointer, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_pointer(pointer, event, &pointer_data, winit_state);
handlers::handle_pointer(
pointer,
event,
&pointer_data,
winit_state,
pointer_seat.clone(),
);
},
);
// Setup relative_pointer if it's available.
let relative_pointer = match relative_pointer_manager.as_ref() {
Some(relative_pointer_manager) => {
Some(init_relative_pointer(&relative_pointer_manager, &*pointer))
}
None => None,
};
let relative_pointer = relative_pointer_manager
.as_ref()
.map(|relative_pointer_manager| {
init_relative_pointer(relative_pointer_manager, &*pointer)
});
Self {
pointer,
relative_pointer,
confined_pointer,
locked_pointer,
}
}
}
@@ -208,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();
@@ -219,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);
@@ -234,9 +322,22 @@ pub(super) fn init_confined_pointer(
pointer: &WlPointer,
) -> ZwpConfinedPointerV1 {
let confined_pointer =
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent.to_raw());
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent);
confined_pointer.quick_assign(move |_, _, _| {});
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,54 @@ 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);
// Clear preedit at the start of `Done`.
event_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
// Send `Commit`.
if let Some(text) = inner.pending_commit.take() {
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Send preedit.
if let Some(preedit) = inner.pending_preedit.take() {
let cursor_range = preedit
.cursor_begin
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
event_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
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,21 +7,21 @@ use sctk::reexports::client::Display;
use sctk::reexports::calloop;
use sctk::window::{
ARGBColor, ButtonColorSpec, ColorSpec, ConceptConfig, ConceptFrame, Decorations,
use raw_window_handle::{
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
};
use raw_window_handle::unix::WaylandHandle;
use sctk::window::Decorations;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::unix::{ARGBColor as LocalARGBColor, Button, ButtonState, Element, Theme};
use crate::platform_impl::{
MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{CursorIcon, Fullscreen, WindowAttributes};
use crate::window::{
CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes,
};
use super::env::WindowingFeatures;
use super::event_loop::WinitState;
@@ -30,7 +30,15 @@ use super::{EventLoopWindowTarget, WindowId};
pub mod shim;
use shim::{WindowHandle, WindowRequest, WindowUpdate};
use shim::{WindowCompositorUpdate, WindowHandle, WindowRequest, WindowUserRequest};
#[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.
@@ -54,15 +62,27 @@ pub struct Window {
/// Fullscreen state.
fullscreen: Arc<AtomicBool>,
/// Maximized state.
maximized: Arc<AtomicBool>,
/// Available windowing features.
windowing_features: WindowingFeatures,
/// 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,
@@ -72,13 +92,22 @@ 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();
let mut window_compositor_update = winit_state
.window_compositor_updates
.get_mut(&window_id)
.unwrap();
// Mark that we need a frame refresh on the DPI change.
winit_state
.window_user_requests
.get_mut(&window_id)
.unwrap()
.refresh_frame = true;
// Set pending scale factor.
window_update.scale_factor = Some(scale);
window_update.redraw_requested = true;
window_compositor_update.scale_factor = Some(scale);
surface.set_buffer_scale(scale);
})
@@ -87,6 +116,8 @@ impl Window {
let scale_factor = sctk::get_surface_scale_factor(&surface);
let window_id = super::make_wid(&surface);
let maximized = Arc::new(AtomicBool::new(false));
let maximized_clone = maximized.clone();
let fullscreen = Arc::new(AtomicBool::new(false));
let fullscreen_clone = fullscreen.clone();
@@ -98,7 +129,7 @@ impl Window {
let theme_manager = event_loop_window_target.theme_manager.clone();
let mut window = event_loop_window_target
.env
.create_window::<ConceptFrame, _>(
.create_window::<WinitFrame, _>(
surface.clone(),
Some(theme_manager),
(width, height),
@@ -106,30 +137,53 @@ impl Window {
use sctk::window::{Event, State};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
let mut window_compositor_update = winit_state
.window_compositor_updates
.get_mut(&window_id)
.unwrap();
let mut window_user_requests = winit_state
.window_user_requests
.get_mut(&window_id)
.unwrap();
match event {
Event::Refresh => {
window_update.refresh_frame = true;
window_user_requests.refresh_frame = true;
}
Event::Configure { new_size, states } => {
let is_maximized = states.contains(&State::Maximized);
maximized_clone.store(is_maximized, Ordering::Relaxed);
let is_fullscreen = states.contains(&State::Fullscreen);
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
window_update.refresh_frame = true;
window_update.redraw_requested = true;
window_user_requests.refresh_frame = true;
if let Some((w, h)) = new_size {
window_update.size = Some(LogicalSize::new(w, h));
window_compositor_update.size = Some(LogicalSize::new(w, h));
}
}
Event::Close => {
window_update.close_window = true;
window_compositor_update.close_window = true;
}
}
},
)
.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);
@@ -150,8 +204,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.
@@ -183,40 +237,65 @@ 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.
let mut window_update = WindowUpdate::new();
window_update.refresh_frame = true;
window_update.redraw_requested = true;
let mut window_user_request = WindowUserRequest::new();
window_user_request.refresh_frame = true;
window_user_request.redraw_requested = true;
let window_id = super::make_wid(&surface);
let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64)));
// Create a handle that performs all the requests on underlying sctk a window.
let window_handle = WindowHandle::new(window, size.clone(), window_requests.clone());
let window_handle = WindowHandle::new(
&event_loop_window_target.env,
window,
size.clone(),
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
.window_updates
.insert(window_id, WindowUpdate::new());
.event_sink
.push_window_event(crate::event::WindowEvent::Focused(false), window_id);
// Add state for the window.
winit_state
.window_user_requests
.insert(window_id, window_user_request);
winit_state
.window_compositor_updates
.insert(window_id, WindowCompositorUpdate::new());
let windowing_features = event_loop_window_target.windowing_features;
// Send all updates to the server.
let wayland_source = &event_loop_window_target.wayland_source;
let event_loop_handle = &event_loop_window_target.event_loop_handle;
// To make our window usable for drawing right away we must `ack` a `configure`
// from the server, the acking part here is done by SCTK window frame, so we just
// need to sync with server so it'll be done automatically for us.
event_loop_handle.with_source(&wayland_source, |event_queue| {
let event_queue = event_queue.queue();
{
let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
let event_queue = wayland_source.queue();
let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!());
});
}
// We all praise GNOME for these 3 lines of pure magic. If we don't do that,
// GNOME will shrink our window a bit for the size of the decorations. I guess it
@@ -235,7 +314,11 @@ impl Window {
window_requests,
event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(),
fullscreen,
maximized,
windowing_features,
resizeable: AtomicBool::new(attributes.resizable),
decorated: AtomicBool::new(attributes.decorations),
cursor_grab_mode: Mutex::new(CursorGrabMode::None),
};
Ok(window)
@@ -250,9 +333,7 @@ impl Window {
#[inline]
pub fn set_title(&self, title: &str) {
let title_request = WindowRequest::Title(title.to_owned());
self.window_requests.lock().unwrap().push(title_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::Title(title.to_owned()));
}
#[inline]
@@ -260,6 +341,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())
@@ -284,9 +370,7 @@ impl Window {
#[inline]
pub fn request_redraw(&self) {
let redraw_request = WindowRequest::Redraw;
self.window_requests.lock().unwrap().push(redraw_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::Redraw);
}
#[inline]
@@ -304,12 +388,7 @@ impl Window {
let size = size.to_logical::<u32>(scale_factor);
*self.size.lock().unwrap() = size;
let frame_size_request = WindowRequest::FrameSize(size);
self.window_requests
.lock()
.unwrap()
.push(frame_size_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::FrameSize(size));
}
#[inline]
@@ -317,9 +396,7 @@ impl Window {
let scale_factor = self.scale_factor() as f64;
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
let min_size_request = WindowRequest::MinSize(size);
self.window_requests.lock().unwrap().push(min_size_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::MinSize(size));
}
#[inline]
@@ -327,19 +404,18 @@ impl Window {
let scale_factor = self.scale_factor() as f64;
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
let max_size_request = WindowRequest::MaxSize(size);
self.window_requests.lock().unwrap().push(max_size_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::MaxSize(size));
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
let resizeable_request = WindowRequest::Resizeable(resizable);
self.window_requests
.lock()
.unwrap()
.push(resizeable_request);
self.event_loop_awakener.ping();
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]
@@ -351,9 +427,18 @@ impl Window {
#[inline]
pub fn set_decorations(&self, decorate: bool) {
let decorate_request = WindowRequest::Decorate(decorate);
self.window_requests.lock().unwrap().push(decorate_request);
self.event_loop_awakener.ping();
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]
@@ -363,16 +448,17 @@ impl Window {
return;
}
let minimize_request = WindowRequest::Minimize;
self.window_requests.lock().unwrap().push(minimize_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::Minimize);
}
#[inline]
pub fn is_maximized(&self) -> bool {
self.maximized.load(Ordering::Relaxed)
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let maximize_request = WindowRequest::Maximize(maximized);
self.window_requests.lock().unwrap().push(maximize_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::Maximize(maximized));
}
#[inline]
@@ -408,194 +494,84 @@ impl Window {
None => WindowRequest::UnsetFullscreen,
};
self.window_requests
.lock()
.unwrap()
.push(fullscreen_request);
self.event_loop_awakener.ping();
}
#[inline]
pub fn set_theme<T: Theme>(&self, theme: T) {
// First buttons is minimize, then maximize, and then close.
let buttons: Vec<(ButtonColorSpec, ButtonColorSpec)> =
[Button::Minimize, Button::Maximize, Button::Close]
.iter()
.map(|button| {
let button = *button;
let idle_active_bg = theme
.button_color(button, ButtonState::Idle, false, true)
.into();
let idle_inactive_bg = theme
.button_color(button, ButtonState::Idle, false, false)
.into();
let idle_active_icon = theme
.button_color(button, ButtonState::Idle, true, true)
.into();
let idle_inactive_icon = theme
.button_color(button, ButtonState::Idle, true, false)
.into();
let idle_bg = ColorSpec {
active: idle_active_bg,
inactive: idle_inactive_bg,
};
let idle_icon = ColorSpec {
active: idle_active_icon,
inactive: idle_inactive_icon,
};
let hovered_active_bg = theme
.button_color(button, ButtonState::Hovered, false, true)
.into();
let hovered_inactive_bg = theme
.button_color(button, ButtonState::Hovered, false, false)
.into();
let hovered_active_icon = theme
.button_color(button, ButtonState::Hovered, true, true)
.into();
let hovered_inactive_icon = theme
.button_color(button, ButtonState::Hovered, true, false)
.into();
let hovered_bg = ColorSpec {
active: hovered_active_bg,
inactive: hovered_inactive_bg,
};
let hovered_icon = ColorSpec {
active: hovered_active_icon,
inactive: hovered_inactive_icon,
};
let disabled_active_bg = theme
.button_color(button, ButtonState::Disabled, false, true)
.into();
let disabled_inactive_bg = theme
.button_color(button, ButtonState::Disabled, false, false)
.into();
let disabled_active_icon = theme
.button_color(button, ButtonState::Disabled, true, true)
.into();
let disabled_inactive_icon = theme
.button_color(button, ButtonState::Disabled, true, false)
.into();
let disabled_bg = ColorSpec {
active: disabled_active_bg,
inactive: disabled_inactive_bg,
};
let disabled_icon = ColorSpec {
active: disabled_active_icon,
inactive: disabled_inactive_icon,
};
let button_bg = ButtonColorSpec {
idle: idle_bg,
hovered: hovered_bg,
disabled: disabled_bg,
};
let button_icon = ButtonColorSpec {
idle: idle_icon,
hovered: hovered_icon,
disabled: disabled_icon,
};
(button_icon, button_bg)
})
.collect();
let minimize_button = Some(buttons[0]);
let maximize_button = Some(buttons[1]);
let close_button = Some(buttons[2]);
// The first color is bar, then separator, and then text color.
let titlebar_colors: Vec<ColorSpec> = [Element::Bar, Element::Separator, Element::Text]
.iter()
.map(|element| {
let element = *element;
let active = theme.element_color(element, true).into();
let inactive = theme.element_color(element, false).into();
ColorSpec { active, inactive }
})
.collect();
let primary_color = titlebar_colors[0];
let secondary_color = titlebar_colors[1];
let title_color = titlebar_colors[2];
let title_font = theme.font();
let concept_config = ConceptConfig {
primary_color,
secondary_color,
title_color,
title_font,
minimize_button,
maximize_button,
close_button,
};
let theme_request = WindowRequest::Theme(concept_config);
self.window_requests.lock().unwrap().push(theme_request);
self.event_loop_awakener.ping();
self.send_request(fullscreen_request);
}
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let cursor_icon_request = WindowRequest::NewCursorIcon(cursor);
self.window_requests
.lock()
.unwrap()
.push(cursor_icon_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::NewCursorIcon(cursor));
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
let cursor_visible_request = WindowRequest::ShowCursor(visible);
self.window_requests
.lock()
.unwrap()
.push(cursor_visible_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::ShowCursor(visible));
}
#[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()));
}
let cursor_grab_request = WindowRequest::GrabCursor(grab);
self.window_requests
.lock()
.unwrap()
.push(cursor_grab_request);
self.event_loop_awakener.ping();
*self.cursor_grab_mode.lock().unwrap() = mode;
self.send_request(WindowRequest::SetCursorGrabMode(mode));
Ok(())
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
if !self.windowing_features.xdg_activation() {
warn!("`request_user_attention` isn't supported");
return;
}
self.send_request(WindowRequest::Attention(request_type));
}
#[inline]
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]
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 drag_window(&self) -> Result<(), ExternalError> {
self.send_request(WindowRequest::DragWindow);
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);
let ime_position_request = WindowRequest::IMEPosition(position);
self.window_requests
.lock()
.unwrap()
.push(ime_position_request);
self.event_loop_awakener.ping();
self.send_request(WindowRequest::ImePosition(position));
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
self.send_request(WindowRequest::AllowIme(allowed));
}
#[inline]
@@ -625,32 +601,38 @@ impl Window {
}
#[inline]
pub fn raw_window_handle(&self) -> WaylandHandle {
let display = self.display.get_display_ptr() as *mut _;
let surface = self.surface.as_ref().c_ptr() as *mut _;
WaylandHandle {
display,
surface,
..WaylandHandle::empty()
}
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)
}
}
impl From<LocalARGBColor> for ARGBColor {
fn from(color: LocalARGBColor) -> Self {
let a = color.a;
let r = color.r;
let g = color.g;
let b = color.b;
Self { a, r, g, b }
#[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]
fn send_request(&self, request: WindowRequest) {
self.window_requests.lock().unwrap().push(request);
self.event_loop_awakener.ping();
}
}
impl Drop for Window {
fn drop(&mut self) {
let close_request = WindowRequest::Close;
self.window_requests.lock().unwrap().push(close_request);
self.event_loop_awakener.ping();
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,18 +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::window::{ConceptConfig, ConceptFrame, Decorations, Window};
use sctk::environment::Environment;
use sctk::window::{Decorations, Window};
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::WindowEvent;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::env::WinitEnv;
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;
use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType};
use super::WinitFrame;
/// A request to SCTK window from Winit window.
#[derive(Debug, Clone)]
@@ -31,8 +41,14 @@ 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,
/// Maximize the window.
Maximize(bool),
@@ -43,6 +59,9 @@ pub enum WindowRequest {
/// Request decorations change.
Decorate(bool),
/// Request decorations change.
CsdThemeVariant(Theme),
/// Make the window resizeable.
Resizeable(bool),
@@ -59,68 +78,58 @@ 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,
/// A new theme for a concept frame was requested.
Theme(ConceptConfig),
/// Window should be closed.
Close,
}
/// Pending update to a window from SCTK window.
#[derive(Debug, Clone, Copy)]
pub struct WindowUpdate {
// The window update comming from the compositor.
#[derive(Default, Debug, Clone, Copy)]
pub struct WindowCompositorUpdate {
/// New window size.
pub size: Option<LogicalSize<u32>>,
/// New scale factor.
pub scale_factor: Option<i32>,
/// Close the window.
pub close_window: bool,
}
impl WindowCompositorUpdate {
pub fn new() -> Self {
Default::default()
}
}
/// Pending update to a window requested by the user.
#[derive(Default, Debug, Clone, Copy)]
pub struct WindowUserRequest {
/// Whether `redraw` was requested.
pub redraw_requested: bool,
/// Wether the frame should be refreshed.
pub refresh_frame: bool,
/// Close the window.
pub close_window: bool,
}
impl WindowUpdate {
impl WindowUserRequest {
pub fn new() -> Self {
Self {
size: None,
scale_factor: None,
redraw_requested: false,
refresh_frame: false,
close_window: false,
}
}
pub fn take(&mut self) -> Self {
let size = self.size.take();
let scale_factor = self.scale_factor.take();
let redraw_requested = self.redraw_requested;
self.redraw_requested = false;
let refresh_frame = self.refresh_frame;
self.refresh_frame = false;
let close_window = self.close_window;
self.close_window = false;
Self {
size,
scale_factor,
redraw_requested,
refresh_frame,
close_window,
}
Default::default()
}
}
@@ -128,7 +137,7 @@ impl WindowUpdate {
/// and react to events.
pub struct WindowHandle {
/// An actual window.
pub window: Window<ConceptFrame>,
pub window: ManuallyDrop<Window<WinitFrame>>,
/// The current size of the window.
pub size: Arc<Mutex<LogicalSize<u32>>>,
@@ -139,64 +148,148 @@ 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>,
/// Text inputs on the current surface.
text_inputs: Vec<TextInputHandler>,
/// XdgActivation object.
xdg_activation: Option<Attached<XdgActivationV1>>,
/// Indicator whether user attention is requested.
attention_requested: Cell<bool>,
/// Compositor
compositor: Attached<WlCompositor>,
}
impl WindowHandle {
pub fn new(
window: Window<ConceptFrame>,
env: &Environment<WinitEnv>,
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,
Some(xdg_activation) => xdg_activation,
};
// Urgency is only removed by the compositor and there's no need to raise urgency when it
// was already raised.
if request_type.is_none() || self.attention_requested.get() {
return;
}
let xdg_activation_token = xdg_activation.get_activation_token();
let surface = self.window.surface();
let window_id = wayland::make_wid(surface);
let xdg_activation = xdg_activation.clone();
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
let token = match event {
xdg_activation_token_v1::Event::Done { token } => token,
_ => return,
};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
let surface = window_handle.window.surface();
xdg_activation.activate(token, surface);
// Mark that attention request was done and drop the token.
window_handle.attention_requested.replace(false);
xdg_token.destroy();
});
xdg_activation_token.set_surface(surface);
xdg_activation_token.commit();
self.attention_requested.replace(true);
}
/// Pointer appeared over the window.
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
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);
}
@@ -211,20 +304,17 @@ 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(),
}
}
}
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
if self
.text_inputs
.iter()
.find(|t| *t == &text_input)
.is_none()
{
if !self.text_inputs.iter().any(|t| *t == text_input) {
self.text_inputs.push(text_input);
}
}
@@ -245,6 +335,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 {
@@ -268,18 +393,26 @@ impl WindowHandle {
pointer.set_cursor(Some(cursor_icon));
}
}
pub fn drag_window(&self) {
for pointer in self.pointers.iter() {
pointer.drag_window(&self.window);
}
}
}
#[inline]
pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_map = &mut winit_state.window_map;
let window_updates = &mut winit_state.window_updates;
let window_user_requests = &mut winit_state.window_user_requests;
let window_compositor_updates = &mut winit_state.window_compositor_updates;
let mut windows_to_close: Vec<WindowId> = Vec::new();
// 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());
@@ -293,11 +426,21 @@ 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();
}
WindowRequest::Maximize(maximize) => {
if maximize {
@@ -318,55 +461,73 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
window_handle.window.set_decorate(decorations);
// We should refresh the frame to apply decorations change.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
#[cfg(feature = "sctk-adwaita")]
WindowRequest::CsdThemeVariant(theme) => {
window_handle.window.set_frame_config(theme.into());
let window_requst = window_user_requests.get_mut(window_id).unwrap();
window_requst.refresh_frame = true;
}
#[cfg(not(feature = "sctk-adwaita"))]
WindowRequest::CsdThemeVariant(_) => {}
WindowRequest::Resizeable(resizeable) => {
window_handle.window.set_resizable(resizeable);
// We should refresh the frame to update button state.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Title(title) => {
window_handle.window.set_title(title);
// We should refresh the frame to draw new title.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::MinSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_min_size(size);
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.redraw_requested = true;
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::MaxSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_max_size(size);
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.redraw_requested = true;
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = 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;
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::PassthroughMouseInput(passthrough) => {
window_handle.passthrough_mouse_input(passthrough);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Attention(request_type) => {
window_handle.set_user_attention(request_type);
}
WindowRequest::Redraw => {
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.redraw_requested = true;
}
WindowRequest::Theme(concept_config) => {
window_handle.window.set_frame_config(concept_config);
// We should refresh the frame to apply new theme.
let window_update = window_updates.get_mut(&window_id).unwrap();
window_update.refresh_frame = true;
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.redraw_requested = true;
}
WindowRequest::Close => {
// The window was requested to be closed.
@@ -383,6 +544,18 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
// Close the windows.
for window in windows_to_close {
let _ = window_map.remove(&window);
let _ = window_updates.remove(&window);
let _ = window_user_requests.remove(&window);
let _ = window_compositor_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,31 @@ 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::Preedit(String::new(), None)),
};
callback(event);
let event = Event::WindowEvent {
window_id,
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 +721,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 +802,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 +918,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 +955,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 +969,7 @@ impl<T: 'static> EventProcessor<T> {
if !self.window_exists(xev.event) {
return;
}
wt.ime
.borrow_mut()
.unfocus(xev.event)
@@ -947,9 +978,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,
@@ -1164,68 +1197,116 @@ impl<T: 'static> EventProcessor<T> {
if let Some(prev_list) = prev_list {
let new_list = wt.xconn.available_monitors();
for new_monitor in new_list {
prev_list
// Previous list may be empty, in case of disconnecting and
// reconnecting the only one monitor. We still need to emit events in
// this case.
let maybe_prev_scale_factor = prev_list
.iter()
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
.map(|prev_monitor| {
if new_monitor.scale_factor != prev_monitor.scale_factor {
for (window_id, window) in wt.windows.borrow().iter() {
if let Some(window) = window.upgrade() {
// Check if the window is on this monitor
let monitor = window.current_monitor();
if monitor.name == new_monitor.name {
let (width, height) =
window.inner_size_physical();
let (new_width, new_height) = window
.adjust_for_dpi(
prev_monitor.scale_factor,
new_monitor.scale_factor,
width,
height,
&*window.shared_state.lock(),
);
.map(|prev_monitor| prev_monitor.scale_factor);
if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
for (window_id, window) in wt.windows.borrow().iter() {
if let Some(window) = window.upgrade() {
// Check if the window is on this monitor
let monitor = window.current_monitor();
if monitor.name == new_monitor.name {
let (width, height) = window.inner_size_physical();
let (new_width, new_height) = window.adjust_for_dpi(
// 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 old_inner_size =
PhysicalSize::new(width, height);
let mut new_inner_size =
PhysicalSize::new(new_width, new_height);
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);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
new_inner_size: &mut new_inner_size,
},
});
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_monitor.scale_factor,
new_inner_size: &mut new_inner_size,
},
});
if new_inner_size != old_inner_size {
let (new_width, new_height) =
new_inner_size.into();
window.set_inner_size_physical(
new_width, new_height,
);
}
}
if new_inner_size != old_inner_size {
let (new_width, new_height) = new_inner_size.into();
window
.set_inner_size_physical(new_width, new_height);
}
}
}
});
}
}
}
}
}
}
}
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

@@ -1,4 +1,9 @@
use x11_dl::xmd::CARD32;
pub use x11_dl::{
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
xrandr::*, xrender::*,
};
// Isn't defined by x11_dl
#[allow(non_upper_case_globals)]
pub const IconicState: CARD32 = 3;

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,30 @@ 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);
// Check if the IME was allowed on that context.
let is_allowed = old_context
.as_ref()
.map(|old_context| old_context.is_allowed())
.unwrap_or_default();
// We can't use the style from the old context here, since it may change on reload, so
// pick style from the new XIM based on the old state.
let style = if is_allowed {
new_im.preedit_style
} else {
new_im.none_style
};
let new_context = {
let result = ImeContext::new(xconn, new_im.im, *window, spot);
let result = ImeContext::new(
xconn,
new_im.im,
style,
*window,
spot,
(*inner).event_sender.clone(),
);
if result.is_err() {
let _ = close_im(xconn, new_im.im);
}
@@ -120,7 +143,7 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
// If we've made it this far, everything succeeded.
let _ = (*inner).destroy_all_contexts_if_necessary();
let _ = (*inner).close_im_if_necessary();
(*inner).im = new_im.im;
(*inner).im = Some(new_im);
(*inner).contexts = new_contexts;
(*inner).is_destroyed = false;
(*inner).is_fallback = is_fallback;
@@ -136,13 +159,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 +190,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,109 +1,326 @@
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::input_method::{Style, XIMStyle};
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(crate) ic: ffi::XIC,
pub(crate) ic_spot: ffi::XPoint,
pub(crate) style: Style,
// 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 {
pub unsafe fn new(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: Style,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
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)
} else {
ImeContext::create_ic(xconn, im, window)
};
let client_data = Box::into_raw(Box::new(ImeContextClientData {
window,
event_sender,
text: Vec::new(),
cursor_pos: 0,
}));
let ic = match style as _ {
Style::Preedit(style) => ImeContext::create_preedit_ic(
xconn,
im,
style,
window,
client_data as ffi::XPointer,
),
Style::Nothing(style) => ImeContext::create_nothing_ic(xconn, im, style, window),
Style::None(style) => ImeContext::create_none_ic(xconn, im, style, window),
}
.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 },
style,
_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_ic(
unsafe fn create_none_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
);
if ic.is_null() {
None
} else {
Some(ic)
}
(!ic.is_null()).then(|| ic)
}
unsafe fn create_ic_with_spot(
unsafe fn create_preedit_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
ic_spot: ffi::XPoint,
client_data: ffi::XPointer,
) -> Option<ffi::XIC> {
let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot);
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");
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
pre_edit_attr.ptr,
preedit_attr.ptr,
ptr::null_mut::<()>(),
);
if ic.is_null() {
None
} else {
Some(ic)
}
(!ic.is_null()).then(|| ic)
}
unsafe fn create_nothing_ic(
xconn: &Arc<XConnection>,
im: ffi::XIM,
style: XIMStyle,
window: ffi::Window,
) -> Option<ffi::XIC> {
let ic = (xconn.xlib.XCreateIC)(
im,
ffi::XNInputStyle_0.as_ptr() as *const _,
style,
ffi::XNClientWindow_0.as_ptr() as *const _,
window,
ptr::null_mut::<()>(),
);
(!ic.is_null()).then(|| ic)
}
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
@@ -120,18 +337,38 @@ impl ImeContext {
xconn.check_errors()
}
pub fn is_allowed(&self) -> bool {
!matches!(self.style, Style::None(_))
}
// 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

@@ -1,8 +1,12 @@
use std::{collections::HashMap, mem, ptr, sync::Arc};
use std::{collections::HashMap, mem, sync::Arc};
use super::{ffi, XConnection, XError};
use super::{context::ImeContext, input_method::PotentialInputMethods};
use super::{
context::ImeContext,
input_method::{InputMethod, 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);
@@ -16,12 +20,12 @@ pub unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), X
pub struct ImeInner {
pub xconn: Arc<XConnection>,
// WARNING: this is initially null!
pub im: ffi::XIM,
pub im: Option<InputMethod>,
pub potential_input_methods: PotentialInputMethods,
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,21 +33,26 @@ 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(),
im: None,
potential_input_methods,
contexts: HashMap::new(),
destroy_callback: unsafe { mem::zeroed() },
event_sender,
is_destroyed: false,
is_fallback: false,
}
}
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
if !self.is_destroyed {
close_im(&self.xconn, self.im).map(|_| true)
if !self.is_destroyed && self.im.is_some() {
close_im(&self.xconn, self.im.as_ref().unwrap().im).map(|_| true)
} else {
Ok(false)
}
@@ -58,10 +67,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

@@ -2,18 +2,17 @@ use std::{
env,
ffi::{CStr, CString, IntoStringError},
fmt,
os::raw::c_char,
os::raw::{c_char, c_ulong, c_ushort},
ptr,
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,15 +41,97 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
#[derive(Debug)]
pub struct InputMethod {
pub im: ffi::XIM,
name: String,
pub preedit_style: Style,
pub none_style: Style,
_name: String,
}
impl InputMethod {
fn new(im: ffi::XIM, name: String) -> Self {
InputMethod { im, name }
fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
let mut styles: *mut XIMStyles = std::ptr::null_mut();
// Query the styles supported by the XIM.
unsafe {
if !(xconn.xlib.XGetIMValues)(
im,
ffi::XNQueryInputStyle_0.as_ptr() as *const _,
(&mut styles) as *mut _,
std::ptr::null_mut::<()>(),
)
.is_null()
{
return None;
}
}
let mut preedit_style = None;
let mut none_style = None;
unsafe {
std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
.iter()
.for_each(|style| match *style {
XIM_PREEDIT_STYLE => {
preedit_style = Some(Style::Preedit(*style));
}
XIM_NOTHING_STYLE if preedit_style.is_none() => {
preedit_style = Some(Style::Nothing(*style))
}
XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
_ => (),
});
(xconn.xlib.XFree)(styles.cast());
};
if preedit_style.is_none() && none_style.is_none() {
return None;
}
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
let none_style = none_style.unwrap_or(preedit_style);
Some(InputMethod {
im,
_name: name,
preedit_style,
none_style,
})
}
}
const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
/// Style of the IME context.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Style {
/// Preedit callbacks.
Preedit(XIMStyle),
/// Nothing.
Nothing(XIMStyle),
/// No IME.
None(XIMStyle),
}
impl Default for Style {
fn default() -> Self {
Style::None(XIM_NONE_STYLE)
}
}
#[repr(C)]
#[derive(Debug)]
struct XIMStyles {
count_styles: c_ushort,
supported_styles: *const XIMStyle,
}
pub(crate) type XIMStyle = c_ulong;
#[derive(Debug)]
pub enum InputMethodResult {
/// Input method used locale modifier from `XMODIFIERS` environment variable.
@@ -63,11 +144,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> {
@@ -180,15 +257,15 @@ impl PotentialInputMethod {
pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
let im = unsafe { open_im(xconn, &self.name.c_string) };
self.successful = Some(im.is_some());
im.map(|im| InputMethod::new(im, self.name.string.clone()))
im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
}
}
// 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 +295,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 +326,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 +336,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

@@ -17,11 +17,32 @@ use self::{
callbacks::*,
context::ImeContext,
inner::{close_im, ImeInner},
input_method::PotentialInputMethods,
input_method::{PotentialInputMethods, Style},
};
pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>;
pub type ImeSender = Sender<(ffi::Window, i16, i16)>;
#[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<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 +58,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 {
@@ -64,7 +88,6 @@ impl Ime {
let is_fallback = input_method.is_fallback();
if let Some(input_method) = input_method.ok() {
inner.im = input_method.im;
inner.is_fallback = is_fallback;
unsafe {
let result = set_destroy_callback(&xconn, input_method.im, &*inner)
@@ -74,6 +97,7 @@ impl Ime {
}
result?;
}
inner.im = Some(input_method);
Ok(Ime { xconn, inner })
} else {
Err(ImeCreationError::OpenFailure(inner.potential_input_methods))
@@ -88,13 +112,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 im = self.inner.im.as_ref().unwrap();
let style = if with_preedit {
im.preedit_style
} else {
im.none_style
};
let context = unsafe {
ImeContext::new(
&self.inner.xconn,
im.im,
style,
window,
None,
self.inner.event_sender.clone(),
)?
};
// Check the state on the context, since it could fail to enable or disable preedit.
let event = if matches!(style, Style::None(_)) {
if with_preedit {
debug!("failed to create IME context with preedit support.")
}
ImeEvent::Disabled
} else {
if !with_preedit {
debug!("failed to create IME context without preedit support.")
}
ImeEvent::Enabled
};
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,33 +32,76 @@ use std::{
ptr,
rc::Rc,
slice,
sync::mpsc::{Receiver, Sender, TryRecvError},
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
use mio_extras::channel::{channel, Receiver, SendError, Sender};
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,
};
const X_TOKEN: Token = Token(0);
const USER_TOKEN: Token = Token(1);
const REDRAW_TOKEN: Token = Token(2);
const USER_REDRAW_TOKEN: Token = Token(1);
struct WakeSender<T> {
sender: Sender<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>,
@@ -68,27 +111,31 @@ pub struct EventLoopWindowTarget<T> {
root: ffi::Window,
ime: RefCell<Ime>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
redraw_sender: Sender<WindowId>,
redraw_sender: WakeSender<WindowId>,
device_event_filter: Cell<DeviceEventFilter>,
_marker: ::std::marker::PhantomData<T>,
}
pub struct EventLoop<T: 'static> {
poll: Poll,
waker: Arc<Waker>,
event_processor: EventProcessor<T>,
redraw_channel: Receiver<WindowId>,
user_channel: Receiver<T>,
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>>,
}
pub struct EventLoopProxy<T: 'static> {
user_sender: Sender<T>,
waker: Arc<Waker>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_sender: self.user_sender.clone(),
waker: self.waker.clone(),
}
}
}
@@ -105,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 {
@@ -129,9 +177,9 @@ 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!(format!("Failed to open input method: {:#?}", state));
panic!("Failed to open input method: {:#?}", state);
}
result.expect("Failed to set input method destruction callback")
});
@@ -180,46 +228,36 @@ impl<T: 'static> EventLoop<T> {
mod_keymap.reset_from_x_connection(&xconn);
let poll = Poll::new().unwrap();
let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap());
let (user_sender, user_channel) = channel();
let (redraw_sender, redraw_channel) = channel();
poll.registry()
.register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE)
.unwrap();
poll.register(
&EventedFd(&xconn.x11_fd),
X_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
let (user_sender, user_channel) = std::sync::mpsc::channel();
let (redraw_sender, redraw_channel) = std::sync::mpsc::channel();
poll.register(
&user_channel,
USER_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
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(),
};
poll.register(
&redraw_channel,
REDRAW_TOKEN,
Ready::readable(),
PollOpt::level(),
)
.unwrap();
// 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,
}),
p: super::EventLoopWindowTarget::X(window_target),
_marker: ::std::marker::PhantomData,
});
@@ -229,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
@@ -246,21 +286,21 @@ impl<T: 'static> EventLoop<T> {
event_processor.init_device(ffi::XIAllDevices);
let result = EventLoop {
EventLoop {
poll,
redraw_channel,
user_channel,
user_sender,
waker,
event_processor,
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
user_receiver: PeekableReceiver::from_recv(user_channel),
user_sender,
target,
};
result
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_sender: self.user_sender.clone(),
waker: self.waker.clone(),
}
}
@@ -268,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,
);
}
}
@@ -302,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,
);
}
}
@@ -329,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,
);
}
@@ -339,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,
};
@@ -354,49 +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() {
self.poll.poll(&mut events, timeout).unwrap();
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)
@@ -405,7 +510,6 @@ impl<T: 'static> EventLoop<T> {
{
let target = &self.target;
let mut xev = MaybeUninit::uninit();
let wt = get_xtarget(&self.target);
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
@@ -416,11 +520,9 @@ 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
{
wt.redraw_sender.send(wid).unwrap();
if let Event::RedrawRequested(crate::window::WindowId(wid)) = event {
wt.redraw_sender.sender.send(wid).unwrap();
wt.redraw_sender.waker.wake().unwrap();
} else {
callback(event, window_target, control_flow);
}
@@ -445,17 +547,45 @@ 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> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| {
EventLoopClosed(if let SendError::Disconnected(x) = e {
x
} else {
unreachable!()
})
})
self.user_sender
.send(event)
.map_err(|e| EventLoopClosed(e.0))
.map(|_| self.waker.wake().unwrap())
}
}
@@ -499,20 +629,11 @@ impl<'a> Deref for DeviceInfo<'a> {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
impl DeviceId {
pub unsafe fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId(0)
}
}
@@ -528,12 +649,12 @@ impl Deref for Window {
}
impl Window {
pub fn new<T>(
pub(crate) fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
let window = Arc::new(UnownedWindow::new(&event_loop, attribs, pl_attribs)?);
let window = Arc::new(UnownedWindow::new(event_loop, attribs, pl_attribs)?);
event_loop
.windows
.borrow_mut()
@@ -547,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();
}
@@ -562,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 {
@@ -593,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)))
@@ -601,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.
@@ -622,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,
};
@@ -679,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 {
@@ -27,12 +26,11 @@ impl XConnection {
(self.xlib.XInternAtom)(self.display, name.as_ptr() as *const c_char, ffi::False)
};
if atom == 0 {
let msg = format!(
panic!(
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
name,
self.check_errors(),
);
panic!(msg);
}
/*println!(
"XInternAtom name:{:?} atom:{:?}",

View File

@@ -104,8 +104,8 @@ impl XConnection {
CursorIcon::WResize => load(b"left_side\0"),
CursorIcon::EwResize => load(b"h_double_arrow\0"),
CursorIcon::NsResize => load(b"v_double_arrow\0"),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),

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) }
@@ -190,6 +196,24 @@ impl<'a> NormalHints<'a> {
}
}
pub fn get_position(&self) -> Option<(i32, i32)> {
if has_flag(self.size_hints.flags, ffi::PPosition) {
Some((self.size_hints.x as i32, self.size_hints.y as i32))
} else {
None
}
}
pub fn set_position(&mut self, position: Option<(i32, i32)>) {
if let Some((x, y)) = position {
self.size_hints.flags |= ffi::PPosition;
self.size_hints.x = x as c_int;
self.size_hints.y = y as c_int;
} else {
self.size_hints.flags &= !ffi::PPosition;
}
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {
@@ -299,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

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

View File

@@ -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`.
@@ -27,7 +28,11 @@ pub fn calc_dpi_factor(
// Quantize 1/12 step size
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
assert!(validate_scale_factor(dpi_factor));
dpi_factor
if dpi_factor <= 20. {
dpi_factor
} else {
1.
}
}
impl XConnection {
@@ -35,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();
}
}
}
@@ -76,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,
@@ -160,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;
@@ -191,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 {

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