Compare commits

..

75 Commits

Author SHA1 Message Date
Kirill Chibisov
00b5de0a68 Winit version 0.29.0-beta.1 2023-08-16 12:40:18 +04:00
Kirill Chibisov
80d1e49354 Use beta versions of android crates 2023-08-16 12:39:57 +04:00
Kirill Chibisov
07dd45f8e3 Bump MSRV to 1.65 2023-08-16 12:39:56 +04:00
Mads Marquart
4e6ce00ec5 Improve macOS/iOS/Web thread safety
Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-08-15 13:10:02 +04:00
Kirill Chibisov
65c2482d74 Pin android-activity git dependency 2023-08-15 13:10:02 +04:00
Kirill Chibisov
ba2bfd064f Reexport raw-window-handle in window module
We use raw-window-handle extensive in the public API as well as we
force the users to use it to get some essential data for interop, thus
reexport it.

Fixes: #2913.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
08ad3f19e2 Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users it's not desirable.

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

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

Fixes: #500.
2023-08-15 13:10:02 +04:00
lucasmerlin
e3fbfd6792 Fix touch force for Apple Pencil 2023-08-15 13:10:02 +04:00
John Nunley
c40af0062b Add a way to embed the X11 window into another
Signed-off-by: John Nunley <dev@notgull.net>
Tested-by: Kirill Chibisov <contact@kchibisov.com>
2023-08-15 13:10:02 +04:00
Fredrik Fornwall
511bf53889 iOS: Use NSOperatingSystemVersion from icrate (#3019) 2023-08-15 13:10:02 +04:00
Robert Bragg
7451c4b88c Android: Support unicode character mapping + dead keys
Up until now the Android backend has been directly mapping key codes
which essentially just represent the "physical" cap of the key (quoted
since this also related to virtual keyboards).

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

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

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

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

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

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

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

Fixes: cad327755 (On Wayland, reduce amount of spurious wakeups)
2023-08-15 13:10:02 +04:00
Kirill Chibisov
5d9ce7f5f4 On X11, set visual_id in raw-window-handle
Fixes #2681.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
ef5b71d658 Revert "Propagate error from EventLoop creation" (#3010)
This reverts commit ed26dd58fd.
The patched was merged with a review by accident.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
4ab36f336c Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users ints not desirable.

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

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

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-15 13:10:02 +04:00
Kirill Chibisov
03bf83f45e Remove 'static requirement on run
There's no need to force the static on the users, given that internally
some backends were not using static in the first place.

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

Fixes #1387.
2023-08-15 13:10:02 +04:00
Tobias Hunger
d21395bb3f On Windows, keep window maximized when setting size bounds (#2899) 2023-08-15 13:10:02 +04:00
Géraud-Loup
f69616ac2c On Windows, add option to customize window class name (#2978) 2023-08-15 13:10:02 +04:00
Mads Marquart
645b1ff00f Update objc2 version (#2936)
* Upgrade to objc2 v0.4.0 and icrate v0.0.3

* Fix `touchBar` method

* Use ClassType::alloc

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

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

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

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

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

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

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

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

In practice most applications almost certainly shouldn't care about
AboutToWait because the frequency of event loop iterations is
essentially arbitrary and usually irrelevant.
2023-08-15 13:10:02 +04:00
Robert Bragg
3bf0fa9ec8 Rename LoopDestroyed to LoopExiting
Considering the possibility of re-running an event loop via run_ondemand
then it's more correct to say that the loop is about to exit without
assuming it's going to be destroyed.
2023-08-15 13:10:02 +04:00
François
7de2bc7ae6 iOS: Always set timer when polling to avoid slow waking (#2979)
* Always set timer when polling to avoid slow waking

* add comment and changelog

---------

Co-authored-by: Dusty DeWeese <dustin.deweese@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-08-15 13:10:02 +04:00
Robert Bragg
3f44eb1fd9 Add timeout argument to pump_events
This renames all internal implementations of pump_events_with_timeout
to pump_events and makes them public.

Since all platforms that support pump_events support timeouts there's
no need to have a separate API.
2023-08-15 13:10:02 +04:00
Robert Bragg
456c735bfe Windows: implement pump_events_with_timeout internally 2023-08-15 13:10:02 +04:00
Robert Bragg
973e6ad400 MacOS: implement pump_events_with_timeout internally
This layers pump_events on a pump_events_with_timeout API, like we have
for Linux and Android.

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

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

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

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

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

This fixes the window_ondemand example - by ensuring the window from
the first loop really is destroyed before waiting for 5 seconds
and starting the second loop.
2023-08-15 13:10:02 +04:00
Robert Bragg
a2e1a0ac19 Update CHANGELOG.md 2023-08-15 13:10:02 +04:00
Robert Bragg
f1a64b3155 Add examples/window_ondemand
A minimal example that shows an application running the event loop more
than once via `run_ondemand`

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

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

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

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

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

Fixes: #2709
2023-08-15 13:10:02 +04:00
Robert Bragg
93f5f1ac3c Remove EventLoopExtRunReturn 2023-08-15 13:10:02 +04:00
Robert Bragg
a02c680a87 Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
Wayland:

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

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

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

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

X11:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This updates the documentation for `request_redraw()` to reflect that we
no longer guarantee that `RedrawRequested` events must be dispatched
after `MainEventsCleared`.
2023-08-15 13:10:02 +04:00
Robert Bragg
7a954c7e08 Android: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand 2023-08-15 13:10:02 +04:00
Robert Bragg
164dce2b8a Add EventLoopExtPumpEvents and EventLoopExtRunOnDemand
This adds two new extensions for running a Winit event loop which will
replace `EventLoopExtRunReturn`

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

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

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

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

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

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

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

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

This patch makes no functional change, it only introduces these new
extensions so we can then handle adding platform-specific backends
in separate pull requests, so the work can be landed in stages.
2023-08-15 13:10:02 +04:00
daxpedda
0ba4283c29 Correctly detect that we don't support Emscripten (#2971) 2023-08-15 13:10:02 +04:00
John Nunley
62b4ba8b50 Replace libc with rustix in some modules
Unfortunately this isn't a total removal, for two reasons:

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

First one we can likely resolve in the near future, not so sure about
the second one without using some weird pthreads trick.
2023-08-15 13:10:02 +04:00
Venceslas Duet
afebe2e7d1 On Windows, add drag_resize_window method support (#2966) 2023-08-15 13:10:02 +04:00
Kirill Chibisov
0efcfaf5a9 Add platform::startup_notify for Wayland/X11
The utils in this module should help the users to activate the windows
they create, as well as manage activation tokens environment variables.

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

Fixes #2279.

Co-authored-by: John Nunley <jtnunley01@gmail.com>
2023-08-15 13:10:02 +04:00
Venceslas Duet
d86ce9de9f On Wayland, fix Window::is_decorated with CSD 2023-08-15 13:10:02 +04:00
Kirill Chibisov
7fa7cea700 On Wayland, make the CSD frame double click reliable
It was discovered that on GNOME the click sometimes being swallowed
by the mutter's `wl_pointer::enter/leave` sequences. This was happening
due to `xdg_toplevel::move` making the pointer to leave the surface.

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

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

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

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
2023-08-15 13:10:02 +04:00
daxpedda
692f15c49f On Web, add WindowBuilderExtWebSys::with_append() (#2953) 2023-08-15 13:10:02 +04:00
daxpedda
5366694db2 Improve documentation of WindowBuilderExtWebSys methods (#2952) 2023-08-15 13:10:02 +04:00
John Nunley
7962271faa Replace parts of the Xlib backend with x11-rb 2023-08-15 13:10:02 +04:00
daxpedda
78b5f2feb8 On Web, remove Window::is_dark_mode() (#2951) 2023-08-15 13:10:02 +04:00
daxpedda
4baab2d93e Fix mentions of Wasm (#2950) 2023-08-15 13:10:02 +04:00
daxpedda
79385ecd1f On Web, implement and fix missing methods on Window(Builder) (#2949) 2023-08-15 13:10:02 +04:00
daxpedda
8d18043a3c Add Fullscreen API compatibility for Safari (#2948) 2023-08-15 13:10:02 +04:00
daxpedda
297c3f80eb On Web, cache commonly used values (#2947) 2023-08-15 13:10:02 +04:00
daxpedda
1d80005b91 Increase accuracy of various Web APIs (#2946) 2023-08-15 13:10:02 +04:00
daxpedda
fab0f62c5a Improve Web specific documentation for various APIs (#2941)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-08-15 13:10:02 +04:00
Kirill Chibisov
d83188befd Rename Window::set_inner_size to Window::request_inner_size
Some systems could resize the window immediately and we'd rather
inform the users right away if that was the case, so they could
create e.g. EGLSurface without waiting for resize, which is really
important for Wayland.

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

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

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

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

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

* Convert the rest of WinitView to use interior mutability

* Use interior mutability instead of a Mutex for the CursorState

* Use interior mutability in WinitWindowDelegate
2023-08-15 13:10:02 +04:00
daxpedda
08fe32eac3 Don't unnecessarily clone canvas on Web (#2934) 2023-08-15 13:10:02 +04:00
daxpedda
1cddc96a0b Fix typos on Web (#2933) 2023-08-15 13:10:02 +04:00
175 changed files with 9990 additions and 7575 deletions

View File

@@ -52,7 +52,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust_version: ['1.64.0', stable, nightly]
rust_version: ['1.65.0', stable, nightly]
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -116,7 +116,7 @@ jobs:
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
matrix.rust_version != '1.65.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests
@@ -126,7 +126,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
matrix.rust_version != '1.65.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Lint with clippy
@@ -138,7 +138,7 @@ jobs:
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
matrix.rust_version != '1.65.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
@@ -147,5 +147,5 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
matrix.rust_version != '1.65.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES

View File

@@ -8,6 +8,69 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased
# 0.29.0-beta.1
- **Breaking:** Bump `ndk` version to `0.8.0-beta.0`, ndk-sys to `v0.5.0-beta.0`, `android-activity` to `0.5.0-beta.1`.
- **Breaking:** Bump MSRV from `1.64` to `1.65`.
- Make iOS windows usable from other threads.
- Reexport `raw-window-handle` in `window` module.
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
- On X11, set `visual_id` in returned `raw-window-handle`.
- **Breaking:** on Wayland, dispatching user created wayland queue won't wake up the loop unless winit has event to send back.
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
- `platform::windows::HINSTANCE`.
- `WindowExtWindows::hinstance`.
- `WindowExtWindows::hwnd`.
- `WindowExtIOS::ui_window`.
- `WindowExtIOS::ui_view_controller`.
- `WindowExtIOS::ui_view`.
- `WindowExtMacOS::ns_window`.
- `WindowExtMacOS::ns_view`.
- `EventLoopWindowTargetExtWayland::wayland_display`.
- `WindowExtWayland::wayland_surface`.
- `WindowExtWayland::wayland_display`.
- `WindowExtX11::xlib_window`.
- `WindowExtX11::xlib_display`.
- `WindowExtX11::xlib_screen_id`.
- `WindowExtX11::xcb_connection`.
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- Implement `PartialOrd` and `Ord` for `Key`, `KeyCode`, `NativeKey`, and `NativeKeyCode`.
- Add `ElementState::is_pressed`.
- On Web, implement `WindowEvent::Occluded`.
- On Web, fix touch location to be as accurate as mouse position.
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
- On Web, add Fullscreen API compatibility for Safari.
- On Web, implement `Window::set_(min|max)_inner_size()`.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, implement `Window::focus_window()`.
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Windows, add `drag_resize_window` method support.
- **Breaking** `run() ->!` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_ondemand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- Added `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On iOS, add force data to touch events when using the Apple Pencil.
# 0.29.0-beta.0
- On Web, allow event loops to be recreated with `spawn`.

View File

@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.64.
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
- 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

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.29.0-beta.0"
version = "0.29.0-beta.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -10,7 +10,7 @@ readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.64.0"
rust-version = "1.65.0"
[package.metadata.docs.rs]
features = ["serde"]
@@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
@@ -55,7 +55,7 @@ cursor-icon = "1.0.0"
log = "0.4"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
raw_window_handle = { package = "raw-window-handle", version = "0.5", features = ["std"] }
serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0"
@@ -67,18 +67,45 @@ simple_logger = { version = "2.1.0", default_features = false }
softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
android-activity = "0.4.0"
ndk = "0.7.0"
ndk-sys = "0.4.0"
# Coordinate the next winit release android-activity 0.5 release
android-activity = "0.5.0-beta.1"
ndk = "0.8.0-beta.0"
ndk-sys = "0.5.0-beta.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks`
objc2 = "0.4.1"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22.3"
dispatch = "0.2.0"
[target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.0.4"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSAttributedString",
"Foundation_NSMutableAttributedString",
"Foundation_NSData",
"Foundation_NSDictionary",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSNumber",
]
[target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.0.4"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSSet",
]
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
@@ -113,6 +140,7 @@ features = [
]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
bytemuck = { version = "1.13.1", default-features = false, optional = true }
libc = "0.2.64"
percent-encoding = { version = "2.0", optional = true }
fnv = { version = "1.0.3", optional = true }
@@ -122,7 +150,9 @@ wayland-client = { version = "0.30.0", optional = true }
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
calloop = "0.10.5"
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0"
memmap2 = { version = "0.5.0", optional = true }
@@ -145,6 +175,8 @@ features = [
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'Node',
@@ -155,6 +187,7 @@ features = [
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent'
]

View File

@@ -13,7 +13,9 @@ be used to create both games and applications. It supports the following main gr
- iOS
- Android
- Web
- via WASM
- Chrome
- Firefox
- Safari 13.1+
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features
@@ -117,6 +119,7 @@ If your PR makes notable changes to Winit's features, please update this section
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
@@ -143,9 +146,6 @@ If your PR makes notable changes to Winit's features, please update this section
### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIWindow` object pointer
* Get the `UIViewController` object pointer
* Get the `UIView` object pointer
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
@@ -172,7 +172,7 @@ Legend:
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
@@ -192,13 +192,13 @@ Legend:
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
@@ -215,19 +215,19 @@ Legend:
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor | |❌ |✔️ | |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.29.0-beta.0"
winit = "0.29.0-beta.1"
```
## [Documentation](https://docs.rs/winit)

View File

@@ -8,12 +8,12 @@ fn main() {
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
wasm_platform: { target_family = "wasm" },
wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform)) },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" },
// Native displays.

View File

@@ -4,4 +4,9 @@ disallowed-methods = [
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
]

View File

@@ -3,14 +3,14 @@
mod fill;
#[cfg(any(x11_platform, macos_platform, windows_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use raw_window_handle::HasRawWindowHandle;
use winit::{
dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
window::raw_window_handle::HasRawWindowHandle,
window::{Window, WindowBuilder, WindowId},
};
@@ -36,7 +36,7 @@ fn main() {
let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let parent_window = WindowBuilder::new()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
@@ -46,7 +46,7 @@ fn main() {
println!("parent window: {parent_window:?})");
event_loop.run(move |event: Event<'_, ()>, event_loop, control_flow| {
event_loop.run(move |event: Event<()>, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, window_id } = event {

View File

@@ -27,7 +27,7 @@ enum Mode {
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
println!("Press '1' to switch to Wait mode.");
@@ -36,7 +36,7 @@ fn main() {
println!("Press 'R' to toggle request_redraw() calls.");
println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop)
@@ -95,18 +95,11 @@ fn main() {
},
_ => (),
},
Event::MainEventsCleared => {
Event::AboutToWait => {
if request_redraw && !wait_cancelled && !close_requested {
window.request_redraw();
}
if close_requested {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
Event::RedrawEventsCleared => {
match mode {
Mode::Wait => control_flow.set_wait(),
Mode::WaitUntil => {
@@ -119,8 +112,15 @@ fn main() {
control_flow.set_poll();
}
};
if close_requested {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_title("A fantastic window!");
@@ -54,7 +54,7 @@ fn main() {
}
_ => (),
}
});
})
}
const CURSORS: &[CursorIcon] = &[

View File

@@ -11,9 +11,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
@@ -73,5 +73,5 @@ fn main() {
Event::RedrawRequested(_) => fill::fill_window(&window),
_ => (),
}
});
})
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
@@ -18,7 +18,9 @@ fn main() {
}
SimpleLogger::new().init().unwrap();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
.build()
.unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -52,7 +54,7 @@ fn main() {
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]

View File

@@ -11,9 +11,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
@@ -69,7 +69,7 @@ fn main() {
}
}
_ => (),
});
})
}
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {

View File

@@ -1,6 +1,7 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::Key;
@@ -12,12 +13,14 @@ use winit::platform::macos::WindowExtMacOS;
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut decorations = true;
let mut minimized = false;
let mut with_min_size = false;
let mut with_max_size = false;
let window = WindowBuilder::new()
.with_title("Hello world!")
@@ -46,6 +49,8 @@ fn main() {
println!("- D\tToggle window decorations");
println!("- X\tMaximize window");
println!("- Z\tMinimize window");
println!("- I\tToggle mIn size limit");
println!("- A\tToggle mAx size limit");
event_loop.run(move |event, elwt, control_flow| {
control_flow.set_wait();
@@ -120,6 +125,32 @@ fn main() {
minimized = !minimized;
window.set_minimized(minimized);
}
"i" => {
with_min_size = !with_min_size;
let min_size = if with_min_size {
Some(PhysicalSize::new(100, 100))
} else {
None
};
window.set_min_inner_size(min_size);
eprintln!(
"Min: {with_min_size}: {min_size:?} => {:?}",
window.inner_size()
);
}
"a" => {
with_max_size = !with_max_size;
let max_size = if with_max_size {
Some(PhysicalSize::new(200, 200))
} else {
None
};
window.set_max_inner_size(max_size);
eprintln!(
"Max: {with_max_size}: {max_size:?} => {:?}",
window.inner_size()
);
}
_ => (),
},
_ => (),
@@ -131,5 +162,5 @@ fn main() {
}
_ => {}
}
});
})
}

View File

@@ -11,9 +11,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Your faithful window")
@@ -87,5 +87,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -13,7 +13,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new()
.with_level(LevelFilter::Trace)
.init()
@@ -24,7 +24,7 @@ fn main() {
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
println!("Press F3 to cycle through IME purposes.");
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
@@ -105,5 +105,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -17,12 +17,12 @@ fn main() {
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"]
mod fill;
simple_logger::SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(400.0, 200.0))
@@ -61,5 +61,5 @@ fn main() {
}
_ => (),
};
});
})
}

View File

@@ -7,7 +7,7 @@ use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap();
if let Some(mon) = window.primary_monitor() {

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Mouse Wheel events")
@@ -64,5 +64,5 @@ In other words, the deltas indicate the direction in which to move the content (
}
_ => (),
}
});
})
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;
@@ -17,7 +17,7 @@ fn main() {
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
let window = WindowBuilder::new()
@@ -137,13 +137,15 @@ fn main() {
}),
"q" => window.request_redraw(),
"r" => window.set_resizable(state),
"s" => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
"s" => {
let _ = window.request_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
});
}
"w" => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
@@ -193,9 +195,7 @@ fn main() {
}
_ => {
if let Some(tx) = window_senders.get(&window_id) {
if let Some(event) = event.to_static() {
tx.send(event).unwrap();
}
tx.send(event).unwrap();
}
}
},

View File

@@ -13,9 +13,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut windows = HashMap::new();
for _ in 0..3 {

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -41,5 +41,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::{sync::Arc, thread, time};
use simple_logger::SimpleLogger;
@@ -15,7 +15,7 @@ fn main() {
mod fill;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = {
let window = WindowBuilder::new()
@@ -49,7 +49,7 @@ fn main() {
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]

View File

@@ -12,9 +12,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut resizable = false;
@@ -53,5 +53,5 @@ fn main() {
}
_ => (),
};
});
})
}

View File

@@ -0,0 +1,126 @@
//! Demonstrates the use of startup notifications on Linux.
#[cfg(any(x11_platform, wayland_platform))]
#[path = "./util/fill.rs"]
mod fill;
#[cfg(any(x11_platform, wayland_platform))]
mod example {
use std::collections::HashMap;
use std::rc::Rc;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::Key;
use winit::platform::startup_notify::{
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
};
use winit::window::{Window, WindowBuilder, WindowId};
pub(super) fn main() -> Result<(), impl std::error::Error> {
// Create the event loop and get the activation token.
let event_loop = EventLoop::new().unwrap();
let mut current_token = match event_loop.read_token_from_env() {
Some(token) => Some(token),
None => {
println!("No startup notification token found in environment.");
None
}
};
let mut windows: HashMap<WindowId, Rc<Window>> = HashMap::new();
let mut counter = 0;
let mut create_first_window = false;
event_loop.run(move |event, elwt, flow| {
match event {
Event::Resumed => create_first_window = true,
Event::WindowEvent {
window_id,
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key,
state: ElementState::Pressed,
..
},
..
},
} => {
if logical_key == Key::Character("n".into()) {
if let Some(window) = windows.get(&window_id) {
// Request a new activation token on this window.
// Once we get it we will use it to create a window.
window
.request_activation_token()
.expect("Failed to request activation token.");
}
}
}
Event::WindowEvent {
window_id,
event: WindowEvent::CloseRequested,
} => {
// Remove the window from the map.
windows.remove(&window_id);
if windows.is_empty() {
flow.set_exit();
return;
}
}
Event::WindowEvent {
event: WindowEvent::ActivationTokenDone { token, .. },
..
} => {
current_token = Some(token);
}
Event::RedrawRequested(id) => {
if let Some(window) = windows.get(&id) {
super::fill::fill_window(window);
}
}
_ => {}
}
// See if we've passed the deadline.
if current_token.is_some() || create_first_window {
// Create the initial window.
let window = {
let mut builder =
WindowBuilder::new().with_title(format!("Window {}", counter));
if let Some(token) = current_token.take() {
println!("Creating a window with token {token:?}");
builder = builder.with_activation_token(token);
}
Rc::new(builder.build(elwt).unwrap())
};
// Add the window to the map.
windows.insert(window.id(), window.clone());
counter += 1;
create_first_window = false;
}
flow.set_wait();
})
}
}
#[cfg(any(x11_platform, wayland_platform))]
fn main() -> Result<(), impl std::error::Error> {
example::main()
}
#[cfg(not(any(x11_platform, wayland_platform)))]
fn main() {
println!("This example is only supported on X11 and Wayland platforms.");
}

View File

@@ -11,9 +11,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -75,5 +75,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -16,9 +16,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -47,5 +47,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -8,9 +8,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Touchpad gestures")
@@ -47,5 +47,5 @@ fn main() {
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
})
}

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_decorations(false)
@@ -36,5 +36,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -5,7 +5,7 @@ use winit::event_loop::EventLoop;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let monitor = match event_loop.primary_monitor() {
Some(monitor) => monitor,
None => {

View File

@@ -7,13 +7,16 @@ use winit::{
window::{Fullscreen, WindowBuilder},
};
pub fn main() {
let event_loop = EventLoop::new();
pub fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(wasm_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
#[cfg(wasm_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window);
@@ -29,7 +32,7 @@ pub fn main() {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
Event::AboutToWait => {
window.request_redraw();
}
Event::WindowEvent {
@@ -53,11 +56,14 @@ pub fn main() {
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]
mod wasm {
use std::num::NonZeroU32;
use softbuffer::{Surface, SurfaceExtWeb};
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
@@ -66,24 +72,33 @@ mod wasm {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
#[allow(clippy::main_recursion)]
super::main();
let _ = super::main();
}
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas().unwrap();
let mut surface = Surface::from_canvas(canvas.clone()).unwrap();
surface
.resize(
NonZeroU32::new(canvas.width()).unwrap(),
NonZeroU32::new(canvas.height()).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
buffer.fill(0xFFF0000);
buffer.present().unwrap();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell where the canvas is for debugging purposes.
canvas
.style()
.set_property("background-color", "crimson")
.unwrap();
body.append_child(&canvas).unwrap();
let style = &canvas.style();
style.set_property("margin", "50px").unwrap();
// Use to test interactions with border and padding.
//style.set_property("border", "50px solid black").unwrap();
//style.set_property("padding", "50px").unwrap();
let log_header = document.create_element("h2").unwrap();
log_header.set_text_content(Some("Event Log"));
@@ -100,11 +115,25 @@ mod wasm {
// 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 event = match event {
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
_ => None,
};
if let Some(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:?}")));
let date = js_sys::Date::new_0();
log.set_text_content(Some(&format!(
"{:02}:{:02}:{:02}.{:03}: {event}",
date.get_hours(),
date.get_minutes(),
date.get_seconds(),
date.get_milliseconds(),
)));
log_list
.insert_before(&log, log_list.first_child().as_ref())
.unwrap();

View File

@@ -13,6 +13,7 @@ mod wasm {
dpi::PhysicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::web::WindowBuilderExtWebSys,
window::{Window, WindowBuilder},
};
@@ -30,13 +31,14 @@ This example demonstrates the desired future functionality which will possibly b
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
// When running in a non-wasm environment this would set the window size to 100x100.
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
.with_inner_size(PhysicalSize::new(100, 100))
.with_append(true)
.build(&event_loop)
.unwrap();
@@ -45,7 +47,7 @@ This example demonstrates the desired future functionality which will possibly b
// Render once with the size info we currently have
render_circle(&canvas, window.inner_size());
event_loop.run(move |event, _, control_flow| {
let _ = event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
@@ -72,7 +74,6 @@ This example demonstrates the desired future functionality which will possibly b
canvas
.style()
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
body.append_child(&canvas).unwrap();
let explanation = document.create_element("pre").unwrap();
explanation.set_text_content(Some(EXPLANATION));

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -29,13 +29,15 @@ fn main() {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -14,9 +14,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -71,5 +71,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -14,9 +14,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -139,5 +139,5 @@ fn main() {
}
_ => (),
}
});
})
}

View File

@@ -13,9 +13,9 @@ const BORDER: f64 = 8.0;
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
@@ -52,6 +52,8 @@ fn main() {
} => {
if let Some(dir) = cursor_location {
let _res = window.drag_resize_window(dir);
} else if !window.is_decorated() {
let _res = window.drag_window();
}
}
WindowEvent::KeyboardInput {
@@ -72,7 +74,7 @@ fn main() {
fill::fill_window(&window);
}
_ => (),
});
})
}
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {

View File

@@ -12,7 +12,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
@@ -23,7 +23,7 @@ fn main() {
let icon = load_icon(Path::new(path));
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("An iconic window!")
@@ -48,7 +48,7 @@ fn main() {
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
})
}
fn load_icon(path: &Path) -> Icon {

View File

@@ -0,0 +1,90 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
fn main() -> Result<(), impl std::error::Error> {
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
error::EventLoopError,
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_ondemand::EventLoopExtRunOnDemand,
window::{Window, WindowBuilder, WindowId},
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Default)]
struct App {
window_id: Option<WindowId>,
window: Option<Window>,
}
SimpleLogger::new().init().unwrap();
let mut event_loop = EventLoop::new().unwrap();
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
let mut app = App::default();
event_loop.run_ondemand(move |event, event_loop, control_flow| {
control_flow.set_wait();
println!("Run {idx}: {:?}", event);
if let Some(window) = &app.window {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window.id() == window_id => {
println!("--------------------------------------------------------- Window {idx} CloseRequested");
app.window = None;
}
Event::AboutToWait => window.request_redraw(),
Event::RedrawRequested(_) => {
fill::fill_window(window);
}
_ => (),
}
} else if let Some(id) = app.window_id {
match event {
Event::WindowEvent {
event: WindowEvent::Destroyed,
window_id,
} if id == window_id => {
println!("--------------------------------------------------------- Window {idx} Destroyed");
app.window_id = None;
control_flow.set_exit();
}
_ => (),
}
} else if let Event::Resumed = event {
let window = WindowBuilder::new()
.with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(event_loop)
.unwrap();
app.window_id = Some(window.id());
app.window = Some(window);
}
})
}
run_app(&mut event_loop, 1)?;
println!("--------------------------------------------------------- Finished first loop");
println!("--------------------------------------------------------- Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5));
let ret = run_app(&mut event_loop, 2);
println!("--------------------------------------------------------- Finished second loop");
ret
}
#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]
fn main() {
println!("This example is not supported on this platform");
}

View File

@@ -18,8 +18,8 @@ mod fill;
/// Prints the keyboard events characters received when option_is_alt is true versus false.
/// A left mouse click will toggle option_is_alt.
#[cfg(target_os = "macos")]
fn main() {
let event_loop = EventLoop::new();
fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -58,7 +58,7 @@ fn main() {
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
_ => (),
},
Event::MainEventsCleared => {
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
@@ -66,7 +66,7 @@ fn main() {
}
_ => (),
}
});
})
}
#[cfg(not(target_os = "macos"))]

View File

@@ -0,0 +1,73 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
android_platform,
))]
fn main() -> std::process::ExitCode {
use std::{process::ExitCode, thread::sleep, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
let mut event_loop = EventLoop::new().unwrap();
SimpleLogger::new().init().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
'main: loop {
let timeout = Some(Duration::ZERO);
let status = event_loop.pump_events(timeout, |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{event:?}");
}
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
if let PumpStatus::Exit(exit_code) = status {
break 'main ExitCode::from(exit_code as u8);
}
// Sleep for 1/60 second to simulate application work
//
// Since `pump_events` doesn't block it will be important to
// throttle the loop in the app somehow.
println!("Update()");
sleep(Duration::from_millis(16));
}
}
#[cfg(any(ios_platform, wasm_platform, orbital_platform))]
fn main() {
println!("This platform doesn't support pump_events.");
}

View File

@@ -11,9 +11,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -54,11 +54,11 @@ fn main() {
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Event::MainEventsCleared => window.request_redraw(),
Event::AboutToWait => window.request_redraw(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -1,71 +0,0 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
android_platform,
orbital_platform,
))]
fn main() {
use std::{thread::sleep, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_return::EventLoopExtRunReturn,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
let mut event_loop = EventLoop::new();
SimpleLogger::new().init().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let mut quit = false;
while !quit {
event_loop.run_return(|event, _, control_flow| {
control_flow.set_wait();
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{event:?}");
}
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
quit = true;
}
Event::MainEventsCleared => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
// Sleep for 1/60 second to simulate rendering
println!("rendering");
sleep(Duration::from_millis(16));
}
}
#[cfg(any(ios_platform, wasm_platform))]
fn main() {
println!("This platform doesn't support run_return.");
}

112
examples/window_tabbing.rs Normal file
View File

@@ -0,0 +1,112 @@
#![allow(clippy::single_match)]
#[cfg(target_os = "macos")]
use std::{collections::HashMap, num::NonZeroUsize};
#[cfg(target_os = "macos")]
use simple_logger::SimpleLogger;
#[cfg(target_os = "macos")]
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
window::{Window, WindowBuilder},
};
#[cfg(target_os = "macos")]
#[path = "util/fill.rs"]
mod fill;
#[cfg(target_os = "macos")]
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let mut windows = HashMap::new();
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.set_wait();
match event {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
// This drops the window, causing it to close.
windows.remove(&window_id);
if windows.is_empty() {
control_flow.set_exit();
}
}
WindowEvent::Resized(_) => {
if let Some(window) = windows.get(&window_id) {
window.request_redraw();
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key,
..
},
is_synthetic: false,
..
} => match logical_key.as_ref() {
Key::Character("t") => {
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
let window = WindowBuilder::new()
.with_tabbing_identifier(&tabbing_id)
.build(event_loop)
.unwrap();
println!("Added a new tab: {:?}", window.id());
windows.insert(window.id(), window);
}
Key::Character("w") => {
let _ = windows.remove(&window_id);
}
Key::ArrowRight => {
windows.get(&window_id).unwrap().select_next_tab();
}
Key::ArrowLeft => {
windows.get(&window_id).unwrap().select_previous_tab();
}
Key::Character(ch) => {
if let Ok(index) = ch.parse::<NonZeroUsize>() {
let index = index.get();
// Select the last tab when pressing `9`.
let window = windows.get(&window_id).unwrap();
if index == 9 {
window.select_tab_at_index(window.num_tabs() - 1)
} else {
window.select_tab_at_index(index - 1);
}
}
}
_ => (),
},
_ => (),
}
}
Event::RedrawRequested(window_id) => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
})
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("This example is only supported on MacOS");
}

69
examples/x11_embed.rs Normal file
View File

@@ -0,0 +1,69 @@
//! A demonstration of embedding a winit window in an existing X11 application.
#[cfg(x11_platform)]
#[path = "util/fill.rs"]
mod fill;
#[cfg(x11_platform)]
mod imple {
use super::fill;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::x11::WindowBuilderExtX11,
window::WindowBuilder,
};
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
// First argument should be a 32-bit X11 window ID.
let parent_window_id = std::env::args()
.nth(1)
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new()?;
let window = WindowBuilder::new()
.with_title("An embedded window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id)
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
}
})?;
Ok(())
}
}
#[cfg(not(x11_platform))]
mod imple {
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
println!("This example is only supported on X11 platforms.");
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
imple::entry()
}

View File

@@ -581,3 +581,421 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
Position::Logical(position.cast())
}
}
#[cfg(test)]
mod tests {
use crate::dpi;
use std::collections::HashSet;
macro_rules! test_pixel_int_impl {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_eq!(
<$ty as Pixel>::from_f64(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.9),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i32>(37),
37,
);
}
)*};
}
test_pixel_int_impl! {
test_pixel_int_u8 => u8,
test_pixel_int_u16 => u16,
test_pixel_int_u32 => u32,
test_pixel_int_i8 => i8,
test_pixel_int_i16 => i16
}
macro_rules! assert_approx_eq {
($a:expr, $b:expr $(,)?) => {
assert!(
($a - $b).abs() < 0.001,
"{} is not approximately equal to {}",
$a,
$b
);
};
}
macro_rules! test_pixel_float_impl {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.0),
37.0,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.4),
37.4,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.5),
37.5,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.9),
37.9,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.5),
38,
);
}
)*};
}
test_pixel_float_impl! {
test_pixel_float_f32 => f32,
test_pixel_float_f64 => f64
}
#[test]
fn test_validate_scale_factor() {
assert!(dpi::validate_scale_factor(1.0));
assert!(dpi::validate_scale_factor(2.0));
assert!(dpi::validate_scale_factor(3.0));
assert!(dpi::validate_scale_factor(1.5));
assert!(dpi::validate_scale_factor(0.5));
assert!(!dpi::validate_scale_factor(0.0));
assert!(!dpi::validate_scale_factor(-1.0));
assert!(!dpi::validate_scale_factor(f64::INFINITY));
assert!(!dpi::validate_scale_factor(f64::NAN));
assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY));
}
#[test]
fn test_logical_position() {
let log_pos = dpi::LogicalPosition::new(1.0, 2.0);
assert_eq!(
log_pos.to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
log_pos.to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2));
assert_eq!(
log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0)
);
assert_eq!(
dpi::LogicalPosition::from((2.0, 2.0)),
dpi::LogicalPosition::new(2.0, 2.0)
);
assert_eq!(
dpi::LogicalPosition::from([2.0, 3.0]),
dpi::LogicalPosition::new(2.0, 3.0)
);
let x: (f64, f64) = log_pos.into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = log_pos.into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_physical_position() {
assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::PhysicalPosition::from((2.0, 2.0)),
dpi::PhysicalPosition::new(2.0, 2.0)
);
assert_eq!(
dpi::PhysicalPosition::from([2.0, 3.0]),
dpi::PhysicalPosition::new(2.0, 3.0)
);
let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_logical_size() {
let log_size = dpi::LogicalSize::new(1.0, 2.0);
assert_eq!(
log_size.to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
log_size.to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2));
assert_eq!(
log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0)
);
assert_eq!(
dpi::LogicalSize::from((2.0, 2.0)),
dpi::LogicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::LogicalSize::from([2.0, 3.0]),
dpi::LogicalSize::new(2.0, 3.0)
);
let x: (f64, f64) = log_size.into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = log_size.into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_physical_size() {
assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::PhysicalSize::from((2.0, 2.0)),
dpi::PhysicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::PhysicalSize::from([2.0, 3.0]),
dpi::PhysicalSize::new(2.0, 3.0)
);
let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_size() {
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)),
dpi::Size::Physical(dpi::PhysicalSize::new(1, 2))
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)),
dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0))
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalSize::new(0.5, 1.0)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
let small = dpi::Size::Physical((1, 2).into());
let medium = dpi::Size::Logical((3, 4).into());
let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0));
let large = dpi::Size::Physical((5, 6).into());
assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical);
}
#[test]
fn test_position() {
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)),
dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2))
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)),
dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0))
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalPosition::new(0.5, 1.0)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
}
// Eat coverage for the Debug impls et al
#[test]
fn ensure_attrs_do_not_panic() {
let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalSize::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalSize::<u32>::default());
let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone());
}
}

View File

@@ -2,11 +2,14 @@ use std::{error, fmt};
use crate::platform_impl;
/// An error whose cause it outside Winit's control.
// TODO: Rename
/// An error that may be generated when requesting Winit state
#[derive(Debug)]
pub enum ExternalError {
/// The operation is not supported by the backend.
NotSupported(NotSupportedError),
/// The operation was ignored.
Ignored,
/// The OS cannot perform the operation.
Os(OsError),
}
@@ -25,6 +28,27 @@ pub struct OsError {
error: platform_impl::OsError,
}
/// A general error that may occur while running the Winit event loop
#[derive(Debug)]
pub enum EventLoopError {
/// The operation is not supported by the backend.
NotSupported(NotSupportedError),
/// The OS cannot perform the operation.
Os(OsError),
/// The event loop can't be re-run while it's already running
AlreadyRunning,
/// The event loop can't be re-created.
RecreationAttempt,
/// Application has exit with an error status.
ExitFailure(i32),
}
impl From<OsError> for EventLoopError {
fn from(value: OsError) -> Self {
Self::Os(value)
}
}
impl NotSupportedError {
#[inline]
#[allow(dead_code)]
@@ -60,6 +84,7 @@ impl fmt::Display for ExternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
ExternalError::NotSupported(e) => e.fmt(f),
ExternalError::Ignored => write!(f, "Operation was ignored"),
ExternalError::Os(e) => e.fmt(f),
}
}
@@ -77,6 +102,41 @@ impl fmt::Display for NotSupportedError {
}
}
impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
EventLoopError::AlreadyRunning => write!(f, "EventLoop is already running"),
EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
EventLoopError::NotSupported(e) => e.fmt(f),
EventLoopError::Os(e) => e.fmt(f),
EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
}
}
}
impl error::Error for OsError {}
impl error::Error for ExternalError {}
impl error::Error for NotSupportedError {}
impl error::Error for EventLoopError {}
#[cfg(test)]
mod tests {
#![allow(clippy::redundant_clone)]
use super::*;
// Eat attributes for testing
#[test]
fn ensure_fmt_does_not_panic() {
let _ = format!(
"{:?}, {}",
NotSupportedError::new(),
NotSupportedError::new().clone()
);
let _ = format!(
"{:?}, {}",
ExternalError::NotSupported(NotSupportedError::new()),
ExternalError::NotSupported(NotSupportedError::new())
);
}
}

View File

@@ -16,17 +16,16 @@
//! for e in (window events, user events, device events) {
//! event_handler(e, ..., &mut control_flow);
//! }
//! event_handler(MainEventsCleared, ..., &mut control_flow);
//!
//! for w in (redraw windows) {
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
//! }
//! event_handler(RedrawEventsCleared, ..., &mut control_flow);
//!
//! event_handler(AboutToWait, ..., &mut control_flow);
//! start_cause = wait_if_necessary(control_flow);
//! }
//!
//! event_handler(LoopDestroyed, ..., &mut control_flow);
//! event_handler(LoopExiting, ..., &mut control_flow);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
@@ -34,27 +33,31 @@
//!
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use smol_str::SmolStr;
use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(wasm_platform))]
use std::time::Instant;
use smol_str::SmolStr;
#[cfg(wasm_platform)]
use web_time::Instant;
use crate::error::ExternalError;
#[cfg(doc)]
use crate::window::Window;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event_loop::AsyncRequestSerial,
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
platform_impl,
window::{Theme, WindowId},
window::{ActivationToken, Theme, WindowId},
};
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[derive(Debug, PartialEq)]
pub enum Event<'a, T: 'static> {
#[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> {
/// Emitted when new events arrive from the OS to be processed.
///
/// This event type is useful as a place to put code that should be done before you start
@@ -66,7 +69,7 @@ pub enum Event<'a, T: 'static> {
/// Emitted when the OS sends an event to a winit window.
WindowEvent {
window_id: WindowId,
event: WindowEvent<'a>,
event: WindowEvent,
},
/// Emitted when the OS sends an event to a device.
@@ -84,7 +87,7 @@ pub enum Event<'a, T: 'static> {
///
/// 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,
/// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason,
/// Winit does not currently try to emit pseudo `Suspended` events before the application
/// quits on platforms without an application lifecycle.
///
@@ -129,7 +132,7 @@ pub enum Event<'a, T: 'static> {
///
/// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event
/// with the property [`persisted`] being true, which means that the page is being
/// put in the [´bfcache`] (back/forward cache) - an in-memory cache that stores a
/// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a
/// complete snapshot of a page (including the JavaScript heap) as the user is
/// navigating away.
///
@@ -195,7 +198,7 @@ pub enum Event<'a, T: 'static> {
///
/// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event
/// with the property [`persisted`] being true, which means that the page is being
/// restored from the [´bfcache`] (back/forward cache) - an in-memory cache that
/// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
/// stores a complete snapshot of a page (including the JavaScript heap) as the
/// user is navigating away.
///
@@ -206,121 +209,53 @@ pub enum Event<'a, T: 'static> {
/// [`Suspended`]: Self::Suspended
Resumed,
/// Emitted when all of the event loop's input events have been processed and redraw processing
/// is about to begin.
/// Emitted when the event loop is about to block and wait for new events.
///
/// This event is useful as a place to put your code that should be run after all
/// state-changing events have been handled and you want to do stuff (updating state, performing
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
/// graphics when something changes, it's usually better to do it in response to
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
/// immediately after this event. Programs that draw graphics continuously, like most games,
/// can render here unconditionally for simplicity.
MainEventsCleared,
/// Most applications shouldn't need to hook into this event since there is no real relationship
/// between how often the event loop needs to wake up and the dispatching of any specific events.
///
/// High frequency event sources, such as input devices could potentially lead to lots of wake
/// ups and also lots of corresponding `AboutToWait` events.
///
/// This is not an ideal event to drive application rendering from and instead applications
/// should render in response to [`Event::RedrawRequested`](crate::event::Event::RedrawRequested)
/// events.
AboutToWait,
/// Emitted after [`MainEventsCleared`] when a window should be redrawn.
/// Emitted when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
///
/// 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.
///
///
/// ## Platform-specific
///
/// - **macOS / iOS:** Due to implementation difficulties, this will often, but not always, be
/// emitted directly inside `drawRect:`, with neither a preceding [`MainEventsCleared`] nor
/// subsequent `RedrawEventsCleared`. See [#2640] for work on this.
///
/// [`MainEventsCleared`]: Self::MainEventsCleared
/// [`RedrawEventsCleared`]: Self::RedrawEventsCleared
/// [#2640]: https://github.com/rust-windowing/winit/issues/2640
/// Winit will aggregate duplicate redraw requests into a single event, to
/// help avoid duplicating rendering work.
RedrawRequested(WindowId),
/// 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 irreversible - if this event is emitted, it is guaranteed to be the last event that
/// gets emitted. You generally want to treat this as a "do on quit" event.
LoopDestroyed,
LoopExiting,
}
impl<T: Clone> Clone for Event<'static, T> {
fn clone(&self) -> Self {
use self::Event::*;
match self {
WindowEvent { window_id, event } => WindowEvent {
window_id: *window_id,
event: event.clone(),
},
UserEvent(event) => UserEvent(event.clone()),
DeviceEvent { device_id, event } => DeviceEvent {
device_id: *device_id,
event: event.clone(),
},
NewEvents(cause) => NewEvents(*cause),
MainEventsCleared => MainEventsCleared,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
LoopDestroyed => LoopDestroyed,
Suspended => Suspended,
Resumed => Resumed,
}
}
}
impl<'a, T> Event<'a, T> {
impl<T> Event<T> {
#[allow(clippy::result_large_err)]
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
MainEventsCleared => Ok(MainEventsCleared),
AboutToWait => Ok(AboutToWait),
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
RedrawEventsCleared => Ok(RedrawEventsCleared),
LoopDestroyed => Ok(LoopDestroyed),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
}
}
/// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime.
/// Otherwise, return `None`.
pub fn to_static(self) -> Option<Event<'static, T>> {
use self::Event::*;
match self {
WindowEvent { window_id, event } => event
.to_static()
.map(|event| WindowEvent { window_id, event }),
UserEvent(event) => Some(UserEvent(event)),
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
NewEvents(cause) => Some(NewEvents(cause)),
MainEventsCleared => Some(MainEventsCleared),
RedrawRequested(wid) => Some(RedrawRequested(wid)),
RedrawEventsCleared => Some(RedrawEventsCleared),
LoopDestroyed => Some(LoopDestroyed),
Suspended => Some(Suspended),
Resumed => Some(Resumed),
}
}
}
/// Describes the reason the event loop is resuming.
@@ -354,8 +289,22 @@ pub enum StartCause {
}
/// Describes an event from a [`Window`].
#[derive(Debug, PartialEq)]
pub enum WindowEvent<'a> {
#[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent {
/// The activation token was delivered back and now could be used.
///
#[cfg_attr(
not(any(x11_platform, wayland_platfrom)),
allow(rustdoc::broken_intra_doc_links)
)]
/// Delivered in response to [`request_activation_token`].
///
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
ActivationTokenDone {
serial: AsyncRequestSerial,
token: ActivationToken,
},
/// The size of the window has changed. Contains the client area's new dimensions.
Resized(PhysicalSize<u32>),
@@ -430,6 +379,14 @@ pub enum WindowEvent<'a> {
Ime(Ime),
/// The cursor has moved on the window.
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
CursorMoved {
device_id: DeviceId,
@@ -440,9 +397,25 @@ pub enum WindowEvent<'a> {
},
/// The cursor has entered the window.
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
CursorEntered { device_id: DeviceId },
/// The cursor has left the window.
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
CursorLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred.
@@ -528,7 +501,12 @@ pub enum WindowEvent<'a> {
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **macOS:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
Touch(Touch),
/// The window's scale factor has changed.
@@ -546,7 +524,10 @@ pub enum WindowEvent<'a> {
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
ScaleFactorChanged {
scale_factor: f64,
new_inner_size: &'a mut PhysicalSize<u32>,
/// Handle to update inner size during scale changes.
///
/// See [`InnerSizeWriter`] docs for more details.
inner_size_writer: InnerSizeWriter,
},
/// The system window theme has changed.
@@ -565,208 +546,16 @@ pub enum WindowEvent<'a> {
/// minimised, set invisible, or fully occluded by another window.
///
/// Platform-specific behavior:
/// - **iOS / Android / Web / Wayland / Windows / Orbital:** Unsupported.
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **iOS / Android / Wayland / Windows / Orbital:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
Occluded(bool),
}
impl Clone for WindowEvent<'static> {
fn clone(&self) -> Self {
use self::WindowEvent::*;
return match self {
Resized(size) => Resized(*size),
Moved(pos) => Moved(*pos),
CloseRequested => CloseRequested,
Destroyed => Destroyed,
DroppedFile(file) => DroppedFile(file.clone()),
HoveredFile(file) => HoveredFile(file.clone()),
HoveredFileCancelled => HoveredFileCancelled,
Focused(f) => Focused(*f),
KeyboardInput {
device_id,
event,
is_synthetic,
} => KeyboardInput {
device_id: *device_id,
event: event.clone(),
is_synthetic: *is_synthetic,
},
Ime(preedit_state) => Ime(preedit_state.clone()),
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
CursorMoved {
device_id,
position,
} => CursorMoved {
device_id: *device_id,
position: *position,
},
CursorEntered { device_id } => CursorEntered {
device_id: *device_id,
},
CursorLeft { device_id } => CursorLeft {
device_id: *device_id,
},
MouseWheel {
device_id,
delta,
phase,
} => MouseWheel {
device_id: *device_id,
delta: *delta,
phase: *phase,
},
MouseInput {
device_id,
state,
button,
} => MouseInput {
device_id: *device_id,
state: *state,
button: *button,
},
TouchpadMagnify {
device_id,
delta,
phase,
} => TouchpadMagnify {
device_id: *device_id,
delta: *delta,
phase: *phase,
},
SmartMagnify { device_id } => SmartMagnify {
device_id: *device_id,
},
TouchpadRotate {
device_id,
delta,
phase,
} => TouchpadRotate {
device_id: *device_id,
delta: *delta,
phase: *phase,
},
TouchpadPressure {
device_id,
pressure,
stage,
} => TouchpadPressure {
device_id: *device_id,
pressure: *pressure,
stage: *stage,
},
AxisMotion {
device_id,
axis,
value,
} => AxisMotion {
device_id: *device_id,
axis: *axis,
value: *value,
},
Touch(touch) => Touch(*touch),
ThemeChanged(theme) => ThemeChanged(*theme),
ScaleFactorChanged { .. } => {
unreachable!("Static event can't be about scale factor changing")
}
Occluded(occluded) => Occluded(*occluded),
};
}
}
impl<'a> WindowEvent<'a> {
pub fn to_static(self) -> Option<WindowEvent<'static>> {
use self::WindowEvent::*;
match self {
Resized(size) => Some(Resized(size)),
Moved(position) => Some(Moved(position)),
CloseRequested => Some(CloseRequested),
Destroyed => Some(Destroyed),
DroppedFile(file) => Some(DroppedFile(file)),
HoveredFile(file) => Some(HoveredFile(file)),
HoveredFileCancelled => Some(HoveredFileCancelled),
Focused(focused) => Some(Focused(focused)),
KeyboardInput {
device_id,
event,
is_synthetic,
} => Some(KeyboardInput {
device_id,
event,
is_synthetic,
}),
ModifiersChanged(modifers) => Some(ModifiersChanged(modifers)),
Ime(event) => Some(Ime(event)),
CursorMoved {
device_id,
position,
} => Some(CursorMoved {
device_id,
position,
}),
CursorEntered { device_id } => Some(CursorEntered { device_id }),
CursorLeft { device_id } => Some(CursorLeft { device_id }),
MouseWheel {
device_id,
delta,
phase,
} => Some(MouseWheel {
device_id,
delta,
phase,
}),
MouseInput {
device_id,
state,
button,
} => Some(MouseInput {
device_id,
state,
button,
}),
TouchpadMagnify {
device_id,
delta,
phase,
} => Some(TouchpadMagnify {
device_id,
delta,
phase,
}),
SmartMagnify { device_id } => Some(SmartMagnify { device_id }),
TouchpadRotate {
device_id,
delta,
phase,
} => Some(TouchpadRotate {
device_id,
delta,
phase,
}),
TouchpadPressure {
device_id,
pressure,
stage,
} => Some(TouchpadPressure {
device_id,
pressure,
stage,
}),
AxisMotion {
device_id,
axis,
value,
} => Some(AxisMotion {
device_id,
axis,
value,
}),
Touch(touch) => Some(Touch(touch)),
ThemeChanged(theme) => Some(ThemeChanged(theme)),
ScaleFactorChanged { .. } => None,
Occluded(occluded) => Some(Occluded(occluded)),
}
}
}
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
@@ -1137,7 +926,12 @@ pub enum TouchPhase {
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **macOS:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
@@ -1148,7 +942,7 @@ pub struct Touch {
///
/// ## Platform-specific
///
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
/// - Only available on **iOS** 9.0+, **Windows** 8+, and **Web**.
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
@@ -1225,6 +1019,13 @@ pub enum ElementState {
Released,
}
impl ElementState {
/// True if `self == Pressed`.
pub fn is_pressed(self) -> bool {
self == ElementState::Pressed
}
}
/// Describes a button of a mouse controller.
///
/// ## Platform-specific
@@ -1268,3 +1069,239 @@ pub enum MouseScrollDelta {
/// and move the content right and down (to reveal more things left and up).
PixelDelta(PhysicalPosition<f64>),
}
/// Handle to synchroniously change the size of the window from the
/// [`WindowEvent`].
#[derive(Debug, Clone)]
pub struct InnerSizeWriter {
pub(crate) new_inner_size: Weak<Mutex<PhysicalSize<u32>>>,
}
impl InnerSizeWriter {
#[cfg(not(orbital_platform))]
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_inner_size }
}
/// Try to request inner size which will be set synchroniously on the window.
pub fn request_inner_size(
&mut self,
new_inner_size: PhysicalSize<u32>,
) -> Result<(), ExternalError> {
if let Some(inner) = self.new_inner_size.upgrade() {
*inner.lock().unwrap() = new_inner_size;
Ok(())
} else {
Err(ExternalError::Ignored)
}
}
}
impl PartialEq for InnerSizeWriter {
fn eq(&self, other: &Self) -> bool {
self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr()
}
}
#[cfg(test)]
mod tests {
use crate::event;
use std::collections::{BTreeSet, HashSet};
macro_rules! foreach_event {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = unsafe { event::DeviceId::dummy() };
#[allow(deprecated)]
{
use crate::event::{Event::*, Ime::Enabled, WindowEvent::*};
use crate::window::WindowId;
// Mainline events.
let wid = unsafe { WindowId::dummy() };
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(RedrawRequested(wid));
x(AboutToWait);
x(LoopExiting);
x(Suspended);
x(Resumed);
// Window events.
let with_window_event = |wev| {
x(WindowEvent {
window_id: wid,
event: wev,
})
};
with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(Resized((0, 0).into()));
with_window_event(DroppedFile("x.txt".into()));
with_window_event(HoveredFile("x.txt".into()));
with_window_event(HoveredFileCancelled);
with_window_event(Ime(Enabled));
with_window_event(CursorMoved {
device_id: did,
position: (0, 0).into(),
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(CursorEntered { device_id: did });
with_window_event(CursorLeft { device_id: did });
with_window_event(MouseWheel {
device_id: did,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(MouseInput {
device_id: did,
state: event::ElementState::Pressed,
button: event::MouseButton::Other(0),
});
with_window_event(TouchpadMagnify {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(SmartMagnify { device_id: did });
with_window_event(TouchpadRotate {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure {
device_id: did,
pressure: 0.0,
stage: 0,
});
with_window_event(AxisMotion {
device_id: did,
axis: 0,
value: 0.0,
});
with_window_event(Touch(event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
location: (0.0, 0.0).into(),
id: 0,
force: Some(event::Force::Normalized(0.0)),
}));
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}
#[allow(deprecated)]
{
use event::DeviceEvent::*;
let with_device_event = |dev_ev| {
x(event::Event::DeviceEvent {
device_id: did,
event: dev_ev,
})
};
with_device_event(Added);
with_device_event(Removed);
with_device_event(MouseMotion {
delta: (0.0, 0.0).into(),
});
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
with_device_event(Motion {
axis: 0,
value: 0.0,
});
with_device_event(Button {
button: 0,
state: event::ElementState::Pressed,
});
with_device_event(Text { codepoint: 'a' });
}
}};
}
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event: event::Event<()>| {
let event2 = event.clone();
assert_eq!(event, event2);
})
}
#[test]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>();
if is_user {
assert_eq!(event2, Err(event::Event::UserEvent(())));
} else {
assert!(event2.is_ok());
}
})
}
#[test]
fn test_force_normalize() {
let force = event::Force::Normalized(0.0);
assert_eq!(force.normalized(), 0.0);
let force2 = event::Force::Calibrated {
force: 5.0,
max_possible_force: 2.5,
altitude_angle: None,
};
assert_eq!(force2.normalized(), 2.0);
let force3 = event::Force::Calibrated {
force: 5.0,
max_possible_force: 2.5,
altitude_angle: Some(std::f64::consts::PI / 2.0),
};
assert_eq!(force3.normalized(), 2.0);
}
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event<()>| {
let _ = format!("{:?}", event);
});
let _ = event::StartCause::Init.clone();
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
set.sort_unstable();
let mut set2 = BTreeSet::new();
set2.insert(did);
set2.insert(did);
HashSet::new().insert(event::TouchPhase::Started.clone());
HashSet::new().insert(event::MouseButton::Left.clone());
HashSet::new().insert(event::Ime::Enabled);
let _ = event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
location: (0.0, 0.0).into(),
id: 0,
force: Some(event::Force::Normalized(0.0)),
}
.clone();
let _ = event::Force::Calibrated {
force: 0.0,
max_possible_force: 0.0,
altitude_angle: None,
}
.clone();
}
}

View File

@@ -9,7 +9,7 @@
//! handle events.
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{error, fmt};
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
@@ -18,6 +18,7 @@ use std::time::{Duration, Instant};
#[cfg(wasm_platform)]
use web_time::{Duration, Instant};
use crate::error::EventLoopError;
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
@@ -87,23 +88,23 @@ impl<T> EventLoopBuilder<T> {
/// ***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.
///
/// ## Panics
///
/// Attempting to create the event loop off the main 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.
/// `EventLoopBuilderExt::any_thread` functions are exposed in the relevant
/// [`platform`] module if the target platform supports creating an event
/// loop on any thread.
///
/// ## 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.
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`.
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY`
/// or `DISPLAY` respectively when building the event loop.
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
///
/// [`platform`]: crate::platform
#[cfg_attr(
@@ -115,17 +116,17 @@ impl<T> EventLoopBuilder<T> {
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> EventLoop<T> {
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
panic!("Creating EventLoop multiple times is not supported.");
return Err(EventLoopError::RecreationAttempt);
}
// 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),
Ok(EventLoop {
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?,
_marker: PhantomData,
}
})
}
#[cfg(wasm_platform)]
@@ -148,7 +149,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// Set by the user callback given to the [`EventLoop::run`] method.
///
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Poll`].
///
@@ -156,7 +157,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
///
/// 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
/// Changes are **not** persistent between multiple calls to `run_ondemand` - issuing a new call will
/// reset the control flow to [`Poll`].
///
/// [`ExitWithCode`]: Self::ExitWithCode
@@ -180,7 +181,7 @@ pub enum ControlFlow {
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
/// Send a [`LoopExiting`] event and stop the event loop. This variant is *sticky* - once set,
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
///
@@ -189,12 +190,12 @@ pub enum ControlFlow {
///
/// ## Platform-specific
///
/// - **Android / iOS / WASM:** The supplied exit code is unused.
/// - **Android / iOS / Web:** The supplied exit code is unused.
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
/// [`std::process::exit`].
///
/// [`LoopDestroyed`]: Event::LoopDestroyed
/// [`LoopExiting`]: Event::LoopExiting
/// [`Exit`]: ControlFlow::Exit
ExitWithCode(i32),
}
@@ -268,42 +269,49 @@ impl EventLoop<()> {
///
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
#[inline]
pub fn new() -> EventLoop<()> {
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
EventLoopBuilder::new().build()
}
}
impl Default for EventLoop<()> {
fn default() -> Self {
Self::new()
}
}
impl<T> EventLoop<T> {
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
pub fn with_user_event() -> EventLoop<T> {
pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> {
EventLoopBuilder::<T>::with_user_event().build()
}
/// Hijacks the calling thread and initializes the winit event loop with the provided
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any pending events.
///
/// Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
///
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
/// event loop's behavior.
///
/// 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.
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
/// and any values not passed to this function will *not* be dropped.
///
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
/// for the Javascript exception trick, and to make it clearer that the event loop runs
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
/// current thread of execution like it does on other platforms.
///
/// This function won't be available with `target_feature = "exception-handling"`.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
{
self.event_loop.run(event_handler)
}
@@ -437,3 +445,29 @@ pub enum DeviceEvents {
/// Never capture device events.
Never,
}
/// A unique identifier of the winit's async request.
///
/// This could be used to identify the async request once it's done
/// and a specific action must be taken.
///
/// One of the handling scenarious could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial {
serial: u64,
}
impl AsyncRequestSerial {
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
#[allow(dead_code)]
pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
// NOTE: we rely on wrap around here, while the user may just request
// in the loop u64::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}

View File

@@ -69,7 +69,7 @@
//
// --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
use smol_str::SmolStr;
pub use smol_str::SmolStr;
/// Contains the platform-native physical key identifier
///
@@ -135,7 +135,7 @@ impl std::fmt::Debug for NativeKeyCode {
/// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical
/// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user
/// define keybinds which work in the presence of identifiers we haven't mapped for you yet.
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKey {
Unidentified,
@@ -662,7 +662,7 @@ pub enum KeyCode {
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> {
/// A key string that corresponds to the character typed by the user, taking into account the

View File

@@ -7,7 +7,7 @@
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//! let event_loop = EventLoop::new();
//! let event_loop = EventLoop::new().unwrap();
//! ```
//!
//! Once this is done there are two ways to create a [`Window`]:
@@ -32,12 +32,12 @@
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
//! point [`Event`]`::`[`LoopExiting`] 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
//! most other platforms. However, this model can be re-implemented to an extent with
//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond compatibility reasons.
//!
//!
@@ -48,7 +48,7 @@
//! window::WindowBuilder,
//! };
//!
//! let event_loop = EventLoop::new();
//! let event_loop = EventLoop::new().unwrap();
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
//!
//! event_loop.run(move |event, _, control_flow| {
@@ -69,7 +69,7 @@
//! println!("The close button was pressed; stopping");
//! control_flow.set_exit();
//! },
//! Event::MainEventsCleared => {
//! Event::AboutToWait => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
@@ -83,7 +83,7 @@
//! // Redraw the application.
//! //
//! // It's preferable for applications that do not render continuously to render in
//! // this event rather than in MainEventsCleared, since rendering in here allows
//! // this event rather than in AboutToWait, since rendering in here allows
//! // the program to gracefully handle redraws requested by the OS.
//! },
//! _ => ()
@@ -109,7 +109,7 @@
//! window visible only once you're ready to render into it.
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
@@ -126,7 +126,7 @@
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`LoopExiting`]: event::Event::LoopExiting
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle

View File

@@ -22,27 +22,6 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Returns a pointer to the [`UIWindow`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
fn ui_window(&self) -> *mut c_void;
/// Returns a pointer to the [`UIViewController`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
fn ui_view_controller(&self) -> *mut c_void;
/// Returns a pointer to the [`UIView`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
@@ -97,45 +76,35 @@ pub trait WindowExtIOS {
}
impl WindowExtIOS for Window {
#[inline]
fn ui_window(&self) -> *mut c_void {
self.window.ui_window()
}
#[inline]
fn ui_view_controller(&self) -> *mut c_void {
self.window.ui_view_controller()
}
#[inline]
fn ui_view(&self) -> *mut c_void {
self.window.ui_view()
}
#[inline]
fn set_scale_factor(&self, scale_factor: f64) {
self.window.set_scale_factor(scale_factor)
self.window
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
}
#[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations)
self.window
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
}
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window.set_prefers_home_indicator_hidden(hidden)
self.window
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
}
#[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.window
.set_preferred_screen_edges_deferring_system_gestures(edges)
self.window.maybe_queue_on_main(move |w| {
w.set_preferred_screen_edges_deferring_system_gestures(edges)
})
}
#[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.window.set_prefers_status_bar_hidden(hidden)
self.window
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
}
}

View File

@@ -10,16 +10,6 @@ use crate::{
/// 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.
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.
fn ns_view(&self) -> *mut c_void;
/// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool;
@@ -38,6 +28,28 @@ pub trait WindowExtMacOS {
/// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool);
/// Group windows together by using the same tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn set_tabbing_identifier(&self, identifier: &str);
/// Returns the window's tabbing identifier.
fn tabbing_identifier(&self) -> String;
/// Select next tab.
fn select_next_tab(&self);
/// Select previous tab.
fn select_previous_tab(&self);
/// Select the tab with the given index.
///
/// Will no-op when the index is out of bounds.
fn select_tab_at_index(&self, index: usize);
/// Get the number of tabs in the window tab group.
fn num_tabs(&self) -> usize;
/// Get the window's edit state.
///
/// # Examples
@@ -70,54 +82,80 @@ pub trait WindowExtMacOS {
}
impl WindowExtMacOS for Window {
#[inline]
fn ns_window(&self) -> *mut c_void {
self.window.ns_window()
}
#[inline]
fn ns_view(&self) -> *mut c_void {
self.window.ns_view()
}
#[inline]
fn simple_fullscreen(&self) -> bool {
self.window.simple_fullscreen()
self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen)
self.window
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
}
#[inline]
fn has_shadow(&self) -> bool {
self.window.has_shadow()
self.window.maybe_wait_on_main(|w| w.has_shadow())
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
self.window.set_has_shadow(has_shadow)
self.window
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
}
#[inline]
fn set_tabbing_identifier(&self, identifier: &str) {
self.window
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
}
#[inline]
fn tabbing_identifier(&self) -> String {
self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
}
#[inline]
fn select_next_tab(&self) {
self.window.maybe_queue_on_main(|w| w.select_next_tab())
}
#[inline]
fn select_previous_tab(&self) {
self.window.maybe_queue_on_main(|w| w.select_previous_tab())
}
#[inline]
fn select_tab_at_index(&self, index: usize) {
self.window
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
}
#[inline]
fn num_tabs(&self) -> usize {
self.window.maybe_wait_on_main(|w| w.num_tabs())
}
#[inline]
fn is_document_edited(&self) -> bool {
self.window.is_document_edited()
self.window.maybe_wait_on_main(|w| w.is_document_edited())
}
#[inline]
fn set_document_edited(&self, edited: bool) {
self.window.set_document_edited(edited)
self.window
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window.set_option_as_alt(option_as_alt)
self.window
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
self.window.option_as_alt()
self.window.maybe_wait_on_main(|w| w.option_as_alt())
}
}
@@ -161,6 +199,10 @@ pub trait WindowBuilderExtMacOS {
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
/// Window accepts click-through mouse events.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
/// Defines the window tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn with_tabbing_identifier(self, identifier: &str) -> WindowBuilder;
/// Set how the <kbd>Option</kbd> keys are interpreted.
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
@@ -225,6 +267,14 @@ impl WindowBuilderExtMacOS for WindowBuilder {
self
}
#[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> WindowBuilder {
self.platform_specific
.tabbing_identifier
.replace(tabbing_identifier.to_string());
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
self.platform_specific.option_as_alt = option_as_alt;
@@ -329,6 +379,12 @@ pub trait EventLoopWindowTargetExtMacOS {
fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
fn hide_other_applications(&self);
/// Set whether the system can automatically organize windows into tabs.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
fn set_allows_automatic_window_tabbing(&self, enabled: bool);
/// Returns whether the system can automatically organize windows into tabs.
fn allows_automatic_window_tabbing(&self) -> bool;
}
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
@@ -339,6 +395,14 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_other_applications(&self) {
self.p.hide_other_applications()
}
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
self.p.set_allows_automatic_window_tabbing(enabled);
}
fn allows_automatic_window_tabbing(&self) -> bool {
self.p.allows_automatic_window_tabbing()
}
}
/// Option as alt behavior.

View File

@@ -9,9 +9,10 @@
//! - `windows`
//! - `web`
//!
//! And the following platform-specific module:
//! And the following platform-specific modules:
//!
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
//! - `run_ondemand` (available on `windows`, `unix`, `macos`, `android`)
//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`)
//!
//! However only the module corresponding to the platform you're compiling to will be available.
@@ -23,6 +24,8 @@ pub mod ios;
pub mod macos;
#[cfg(orbital_platform)]
pub mod orbital;
#[cfg(any(x11_platform, wayland_platform))]
pub mod startup_notify;
#[cfg(wayland_platform)]
pub mod wayland;
#[cfg(wasm_platform)]
@@ -32,14 +35,23 @@ pub mod windows;
#[cfg(x11_platform)]
pub mod x11;
pub mod modifier_supplement;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
orbital_platform
wayland_platform
))]
pub mod run_return;
pub mod run_ondemand;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
))]
pub mod pump_events;
pub mod modifier_supplement;
pub mod scancode;

189
src/platform/pump_events.rs Normal file
View File

@@ -0,0 +1,189 @@
use std::time::Duration;
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
/// The return status for `pump_events`
pub enum PumpStatus {
/// Continue running external loop.
Continue,
/// Exit external loop.
Exit(i32),
}
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent;
/// Pump the `EventLoop` to check for and dispatch pending events.
///
/// This API is designed to enable applications to integrate Winit into an
/// external event loop, for platforms that can support this.
///
/// The given `timeout` limits how long it may block waiting for new events.
///
/// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external
/// event loop is never blocked but you would likely need to consider how
/// to throttle your own external loop.
///
/// Passing a `timeout` of `None` means that it may wait indefinitely for new
/// events before returning control back to the external loop.
///
/// ## Example
///
/// ```rust,no_run
/// # // Copied from examples/window_pump_events.rs
/// # #[cfg(any(
/// # windows_platform,
/// # macos_platform,
/// # x11_platform,
/// # wayland_platform,
/// # android_platform,
/// # ))]
/// fn main() -> std::process::ExitCode {
/// # use std::{process::ExitCode, thread::sleep, time::Duration};
/// #
/// # use simple_logger::SimpleLogger;
/// # use winit::{
/// # event::{Event, WindowEvent},
/// # event_loop::EventLoop,
/// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
/// # window::WindowBuilder,
/// # };
/// let mut event_loop = EventLoop::new().unwrap();
/// #
/// # SimpleLogger::new().init().unwrap();
/// let window = WindowBuilder::new()
/// .with_title("A fantastic window!")
/// .build(&event_loop)
/// .unwrap();
///
/// 'main: loop {
/// let timeout = Some(Duration::ZERO);
/// let status = event_loop.pump_events(timeout, |event, _, control_flow| {
/// # if let Event::WindowEvent { event, .. } = &event {
/// # // Print only Window events to reduce noise
/// # println!("{event:?}");
/// # }
/// #
/// match event {
/// Event::WindowEvent {
/// event: WindowEvent::CloseRequested,
/// window_id,
/// } if window_id == window.id() => control_flow.set_exit(),
/// Event::AboutToWait => {
/// window.request_redraw();
/// }
/// _ => (),
/// }
/// });
/// if let PumpStatus::Exit(exit_code) = status {
/// break 'main ExitCode::from(exit_code as u8);
/// }
///
/// // Sleep for 1/60 second to simulate application work
/// //
/// // Since `pump_events` doesn't block it will be important to
/// // throttle the loop in the app somehow.
/// println!("Update()");
/// sleep(Duration::from_millis(16));
/// }
/// }
/// ```
///
/// **Note:** This is not a portable API, and its usage involves a number of
/// caveats and trade offs that should be considered before using this API!
///
/// You almost certainly shouldn't use this API, unless you absolutely know it's
/// the only practical option you have.
///
/// ## Synchronous events
///
/// Some events _must_ only be handled synchronously via the closure that
/// is passed to Winit so that the handler will also be synchronized with
/// the window system and operating system.
///
/// This is because some events are driven by a window system callback
/// where the window systems expects the application to have handled the
/// event before returning.
///
/// **These events can not be buffered and handled outside of the closure
/// passed to Winit.**
///
/// As a general rule it is not recommended to ever buffer events to handle
/// them outside of the closure passed to Winit since it's difficult to
/// provide guarantees about which events are safe to buffer across all
/// operating systems.
///
/// Notable events that will certainly create portability problems if
/// buffered and handled outside of Winit include:
/// - `RedrawRequested` events, used to schedule rendering.
///
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
///
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
///
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
/// - Windows
/// - Linux
/// - MacOS
/// - Android
///
/// ## Unsupported Platforms
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
/// - **Windows**: The implementation will use `PeekMessage` when checking for
/// window messages to avoid blocking your external event loop.
///
/// - **MacOS**: The implementation works in terms of stopping the global `NSApp`
/// whenever the application `RunLoop` indicates that it is preparing to block
/// and wait for new events.
///
/// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private
/// implementation details for `NSApp` which aren't accessible to application
/// developers)
///
/// It's likely this will be less efficient than polling on other OSs and
/// it also means the `NSApp` is stopped while outside of the Winit
/// event loop - and that's observable (for example to crates like `rfd`)
/// because the `NSApp` is global state.
///
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
type UserEvent = T;
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
{
self.event_loop.pump_events(timeout, event_handler)
}
}

View File

@@ -0,0 +1,74 @@
use crate::{
error::EventLoopError,
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
#[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent;
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any window system events.
///
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
/// and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by setting the `control_flow` to [`ControlFlow::Exit`]) and
/// so the event loop can be re-run after it has exit.
///
/// It's expected that each run of the loop will be for orthogonal instantiations of your
/// Winit application, but internally each instantiation may re-use some common window
/// system resources, such as a display server connection.
///
/// This API is not designed to run an event loop in bursts that you can exit from and return
/// to while maintaining the full state of your application. (If you need something like this
/// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API)
///
/// Each time `run_ondemand` is called the `event_handler` can expect to receive a
/// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume
/// lifecycle) - which can be used to consistently initialize application state.
///
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
/// event loop's behavior.
///
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to
/// return to the caller (specifically this is impossible on iOS and Web - though with
/// the Web backend it is possible to use `spawn()` more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need
/// the ability to re-run a single event loop more than once
///
/// # Supported Platforms
/// - Windows
/// - Linux
/// - macOS
/// - Android
///
/// # Unsupported Platforms
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS.
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_ondemand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
{
self.event_loop.run_ondemand(event_handler)
}
}

View File

@@ -1,53 +0,0 @@
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
/// 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`].
type UserEvent;
/// Initializes the `winit` event loop.
///
/// 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
/// `event_handler` closure not running until the resize operation ends. Other OS operations
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
/// 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.
///
/// ## 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>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
}
impl<T> EventLoopExtRunReturn for EventLoop<T> {
type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
{
self.event_loop.run_return(event_handler)
}
}

View File

@@ -0,0 +1,109 @@
//! Window startup notification to handle window raising.
//!
//! The [`ActivationToken`] is essential to ensure that your newly
//! created window will obtain the focus, otherwise the user could
//! be requered to click on the window.
//!
//! Such token is usually delivered via the environment variable and
//! could be read from it with the [`EventLoopExtStartupNotify::read_token_from_env`].
//!
//! Such token must also be reset after reading it from your environment with
//! [`reset_activation_token_env`] otherwise child processes could inherit it.
//!
//! When starting a new child process with a newly obtained [`ActivationToken`] from
//! [`WindowExtStartupNotify::request_activation_token`] the [`set_activation_token_env`]
//! must be used to propagate it to the child
//!
//! To ensure the delivery of such token by other processes to you, the user should
//! set `StartupNotify=true` inside the `.desktop` file of their application.
//!
//! The specification could be found [`here`].
//!
//! [`here`]: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt
use std::env;
use crate::error::NotSupportedError;
use crate::event_loop::{AsyncRequestSerial, EventLoopWindowTarget};
use crate::window::{ActivationToken, Window, WindowBuilder};
/// The variable which is used mostly on X11.
const X11_VAR: &str = "DESKTOP_STARTUP_ID";
/// The variable which is used mostly on Wayland.
const WAYLAND_VAR: &str = "XDG_ACTIVATION_TOKEN";
pub trait EventLoopExtStartupNotify {
/// Read the token from the environment.
///
/// It's recommended **to unset** this environment variable for child processes.
fn read_token_from_env(&self) -> Option<ActivationToken>;
}
pub trait WindowExtStartupNotify {
/// Request a new activation token.
///
/// The token will be delivered inside
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
}
pub trait WindowBuilderExtStartupNotify {
/// Use this [`ActivationToken`] during window creation.
///
/// Not using such a token upon a window could make your window not gaining
/// focus until the user clicks on the window.
fn with_activation_token(self, token: ActivationToken) -> Self;
}
impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> {
fn read_token_from_env(&self) -> Option<ActivationToken> {
match self.p {
#[cfg(wayland_platform)]
crate::platform_impl::EventLoopWindowTarget::Wayland(_) => env::var(WAYLAND_VAR),
#[cfg(x11_platform)]
crate::platform_impl::EventLoopWindowTarget::X(_) => env::var(X11_VAR),
}
.ok()
.map(ActivationToken::_new)
}
}
impl WindowExtStartupNotify for Window {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
self.window.request_activation_token()
}
}
impl WindowBuilderExtStartupNotify for WindowBuilder {
fn with_activation_token(mut self, token: ActivationToken) -> Self {
self.platform_specific.activation_token = Some(token);
self
}
}
/// Remove the activation environment variables from the current process.
///
/// This is wise to do before running child processes,
/// which may not to support the activation token.
///
/// # Safety
///
/// While the function is safe internally, it mutates the global environment
/// state for the process, hence unsafe.
pub unsafe fn reset_activation_token_env() {
env::remove_var(X11_VAR);
env::remove_var(WAYLAND_VAR);
}
/// Set environment variables responsible for activation token.
///
/// This could be used before running daemon processes.
///
/// # Safety
///
/// While the function is safe internally, it mutates the global environment
/// state for the process, hence unsafe.
pub unsafe fn set_activation_token_env(token: ActivationToken) {
env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token._token);
}

View File

@@ -1,17 +1,10 @@
use std::os::raw;
use sctk::reexports::client::Proxy;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
use crate::platform_impl::{ApplicationName, Backend};
pub use crate::window::Theme;
@@ -19,16 +12,6 @@ pub use crate::window::Theme;
pub trait EventLoopWindowTargetExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
fn is_wayland(&self) -> bool;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
@@ -36,17 +19,6 @@ impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.connection.display().id().as_ptr() as *mut _)
}
#[cfg(x11_platform)]
_ => None,
}
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
@@ -76,41 +48,9 @@ impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
}
/// Additional methods on [`Window`] that are specific to Wayland.
pub trait WindowExtWayland {
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
pub trait WindowExtWayland {}
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtWayland for Window {
#[inline]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
}
impl WindowExtWayland for Window {}
/// Additional methods on [`WindowBuilder`] that are specific to Wayland.
pub trait WindowBuilderExtWayland {

View File

@@ -2,24 +2,58 @@
//! 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.
//!
//! It is recommended **not** to apply certain CSS properties to the canvas:
//! - [`transform`]
//! - [`border`]
//! - [`padding`]
//!
//! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
//! - [`WindowEvent::Occluded`]
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`],
//! and [`WindowEvent::Touch`].
//! - [`Window::set_outer_position()`]
//!
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size()
//! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded
//! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
//! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
//! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position()
//! [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
use crate::event::Event;
use crate::event_loop::ControlFlow;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::window::WindowBuilder;
use crate::window::{Window, WindowBuilder};
use web_sys::HtmlCanvasElement;
pub trait WindowExtWebSys {
/// Only returns the canvas if called from inside the window.
fn canvas(&self) -> Option<HtmlCanvasElement>;
}
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
impl WindowExtWebSys for Window {
#[inline]
fn canvas(&self) -> Option<HtmlCanvasElement> {
self.window.canvas()
}
}
pub trait WindowBuilderExtWebSys {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`](crate::window::Window). If
/// [`None`], [`WindowBuilder::build()`] will create one.
///
/// In any case, the canvas won't be automatically inserted into the web page.
///
/// [`None`] by default.
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
@@ -30,11 +64,20 @@ pub trait WindowBuilderExtWebSys {
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
///
/// Enabled by default.
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.
///
/// Enabled by default.
fn with_focusable(self, focusable: bool) -> Self;
/// On window creation, append the canvas element to the web page if it isn't already.
///
/// Disabled by default.
fn with_append(self, append: bool) -> Self;
}
impl WindowBuilderExtWebSys for WindowBuilder {
@@ -55,6 +98,12 @@ impl WindowBuilderExtWebSys for WindowBuilder {
self
}
fn with_append(mut self, append: bool) -> Self {
self.platform_specific.append = append;
self
}
}
/// Additional methods on `EventLoop` that are specific to the web.
@@ -74,11 +123,7 @@ pub trait EventLoopExtWebSys {
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow);
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
@@ -87,11 +132,7 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
+ FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>, &mut ControlFlow),
{
self.event_loop.spawn(event_handler)
}

View File

@@ -17,8 +17,6 @@ pub type HWND = isize;
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 EventLoopBuilderExtWindows {
@@ -113,13 +111,6 @@ impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows {
/// Returns the HINSTANCE of the window
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) -> HWND;
/// Enables or disables mouse and keyboard input to the specified window.
///
/// A window must be enabled before it can be activated.
@@ -148,16 +139,6 @@ pub trait WindowExtWindows {
}
impl WindowExtWindows for Window {
#[inline]
fn hinstance(&self) -> HINSTANCE {
self.window.hinstance()
}
#[inline]
fn hwnd(&self) -> HWND {
self.window.hwnd()
}
#[inline]
fn set_enable(&self, enabled: bool) {
self.window.set_enable(enabled)
@@ -223,6 +204,9 @@ pub trait WindowBuilderExtWindows {
/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
/// Customize the window class name.
fn with_class_name<S: Into<String>>(self, class_name: S) -> WindowBuilder;
/// Shows or hides the background drop shadow for undecorated windows.
///
/// The shadow is hidden by default.
@@ -267,6 +251,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
self
}
#[inline]
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> WindowBuilder {
self.platform_specific.class_name = class_name.into();
self
}
#[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
self.platform_specific.decoration_shadow = shadow;

View File

@@ -1,6 +1,3 @@
use std::os::raw;
use std::ptr;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
@@ -8,9 +5,7 @@ use crate::{
};
use crate::dpi::Size;
use crate::platform_impl::{
x11::ffi::XVisualInfo, ApplicationName, Backend, Window as LinuxWindow, XLIB_ERROR_HOOKS,
};
use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS};
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
@@ -22,6 +17,12 @@ pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupport
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// A unique identifer for an X11 visual.
pub type XVisualID = u32;
/// A unique identifier for an X11 window.
pub type XWindow = u32;
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors comming from X11
@@ -81,70 +82,14 @@ impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
}
/// Additional methods on [`Window`] that are specific to X11.
pub trait WindowExtX11 {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
fn xlib_window(&self) -> Option<raw::c_ulong>;
pub trait WindowExtX11 {}
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xlib_display(&self) -> Option<*mut raw::c_void>;
fn xlib_screen_id(&self) -> Option<raw::c_int>;
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtX11 for Window {
#[inline]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(wayland_platform)]
_ => None,
}
}
}
impl WindowExtX11 for Window {}
/// Additional methods on [`WindowBuilder`] that are specific to X11.
pub trait WindowBuilderExtX11 {
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
/// Create this window with a specific X11 visual.
fn with_x11_visual(self, visual_id: XVisualID) -> Self;
fn with_x11_screen(self, screen_id: i32) -> Self;
@@ -176,21 +121,35 @@ pub trait WindowBuilderExtX11 {
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
/// Embed this window into another parent window.
///
/// # Example
///
/// ```no_run
/// use winit::window::WindowBuilder;
/// use winit::platform::x11::{XWindow, WindowBuilderExtX11};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = winit::event_loop::EventLoop::new().unwrap();
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
/// let window = WindowBuilder::new()
/// .with_embed_parent_window(parent_window_id)
/// .build(&event_loop)?;
/// # Ok(()) }
/// ```
fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self;
}
impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
self.platform_specific.x11.visual_id = Some(visual_id);
self
}
#[inline]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self.platform_specific.x11.screen_id = Some(screen_id);
self
}
@@ -202,19 +161,25 @@ impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self.platform_specific.x11.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self.platform_specific.x11.x11_window_types = x11_window_types;
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self.platform_specific.x11.base_size = Some(base_size.into());
self
}
#[inline]
fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self {
self.platform_specific.x11.embed_window = Some(parent_window_id);
self
}
}

View File

@@ -1,4 +1,7 @@
use android_activity::input::Keycode;
use android_activity::{
input::{KeyAction, KeyEvent, KeyMapChar, Keycode},
AndroidApp,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
@@ -156,324 +159,405 @@ pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
}
}
// TODO: We need to expose getUnicodeChar via android-activity instead of having
// a fixed mapping from key codes
pub fn to_logical(keycode: Keycode, native: NativeKey) -> Key {
/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent
///
/// This takes a `KeyEvent` and looks up its corresponding `KeyCharacterMap` and
/// uses that to try and map the `key_code` + `meta_state` to a unicode
/// character or a dead key that can be combined with the next key press.
pub fn character_map_and_combine_key(
app: &AndroidApp,
key_event: &KeyEvent<'_>,
combining_accent: &mut Option<char>,
) -> Option<KeyMapChar> {
let device_id = key_event.device_id();
let key_map = match app.device_key_character_map(device_id) {
Ok(key_map) => key_map,
Err(err) => {
log::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
return None;
}
};
match key_map.get(key_event.key_code(), key_event.meta_state()) {
Ok(KeyMapChar::Unicode(unicode)) => {
// Only do dead key combining on key down
if key_event.action() == KeyAction::Down {
let combined_unicode = if let Some(accent) = combining_accent {
match key_map.get_dead_char(*accent, unicode) {
Ok(Some(key)) => Some(key),
Ok(None) => None,
Err(err) => {
log::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
None
}
}
} else {
Some(unicode)
};
*combining_accent = None;
combined_unicode.map(KeyMapChar::Unicode)
} else {
Some(KeyMapChar::Unicode(unicode))
}
}
Ok(KeyMapChar::CombiningAccent(accent)) => {
if key_event.action() == KeyAction::Down {
*combining_accent = Some(accent);
}
Some(KeyMapChar::CombiningAccent(accent))
}
Ok(KeyMapChar::None) => {
// Leave any combining_accent state in tact (seems to match how other
// Android apps work)
None
}
Err(err) => {
log::warn!("KeyEvent: Failed to get key map character: {err:?}");
*combining_accent = None;
None
}
}
}
pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
use android_activity::input::Keycode::*;
match keycode {
Unknown => Key::Unidentified(native),
let native = NativeKey::Android(keycode.into());
// Can be added on demand
SoftLeft => Key::Unidentified(native),
SoftRight => Key::Unidentified(native),
match key_char {
Some(KeyMapChar::Unicode(c)) => {
Key::Character(smol_str::SmolStr::from_iter([c].into_iter()))
}
Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)),
None | Some(KeyMapChar::None) => match keycode {
// Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::BrowserHome,
Back => Key::BrowserBack,
Call => Key::Call,
Endcall => Key::EndCall,
// Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::BrowserHome,
Back => Key::BrowserBack,
Call => Key::Call,
Endcall => Key::EndCall,
//-------------------------------------------------------------------------------
// These should be redundant because they should have already been matched
// as `KeyMapChar::Unicode`, but also matched here as a fallback
Keycode0 => Key::Character("0".into()),
Keycode1 => Key::Character("1".into()),
Keycode2 => Key::Character("2".into()),
Keycode3 => Key::Character("3".into()),
Keycode4 => Key::Character("4".into()),
Keycode5 => Key::Character("5".into()),
Keycode6 => Key::Character("6".into()),
Keycode7 => Key::Character("7".into()),
Keycode8 => Key::Character("8".into()),
Keycode9 => Key::Character("9".into()),
Star => Key::Character("*".into()),
Pound => Key::Character("#".into()),
A => Key::Character("a".into()),
B => Key::Character("b".into()),
C => Key::Character("c".into()),
D => Key::Character("d".into()),
E => Key::Character("e".into()),
F => Key::Character("f".into()),
G => Key::Character("g".into()),
H => Key::Character("h".into()),
I => Key::Character("i".into()),
J => Key::Character("j".into()),
K => Key::Character("k".into()),
L => Key::Character("l".into()),
M => Key::Character("m".into()),
N => Key::Character("n".into()),
O => Key::Character("o".into()),
P => Key::Character("p".into()),
Q => Key::Character("q".into()),
R => Key::Character("r".into()),
S => Key::Character("s".into()),
T => Key::Character("t".into()),
U => Key::Character("u".into()),
V => Key::Character("v".into()),
W => Key::Character("w".into()),
X => Key::Character("x".into()),
Y => Key::Character("y".into()),
Z => Key::Character("z".into()),
Comma => Key::Character(",".into()),
Period => Key::Character(".".into()),
Grave => Key::Character("`".into()),
Minus => Key::Character("-".into()),
Equals => Key::Character("=".into()),
LeftBracket => Key::Character("[".into()),
RightBracket => Key::Character("]".into()),
Backslash => Key::Character("\\".into()),
Semicolon => Key::Character(";".into()),
Apostrophe => Key::Character("'".into()),
Slash => Key::Character("/".into()),
At => Key::Character("@".into()),
Plus => Key::Character("+".into()),
//-------------------------------------------------------------------------------
DpadUp => Key::ArrowUp,
DpadDown => Key::ArrowDown,
DpadLeft => Key::ArrowLeft,
DpadRight => Key::ArrowRight,
DpadCenter => Key::Enter,
//-------------------------------------------------------------------------------
// Reporting unidentified, because the specific character is layout dependent.
// (I'm not sure though)
Keycode0 => Key::Character("0".into()),
Keycode1 => Key::Character("1".into()),
Keycode2 => Key::Character("2".into()),
Keycode3 => Key::Character("3".into()),
Keycode4 => Key::Character("4".into()),
Keycode5 => Key::Character("5".into()),
Keycode6 => Key::Character("6".into()),
Keycode7 => Key::Character("7".into()),
Keycode8 => Key::Character("8".into()),
Keycode9 => Key::Character("9".into()),
Star => Key::Character("*".into()),
Pound => Key::Character("#".into()),
A => Key::Character("a".into()),
B => Key::Character("b".into()),
C => Key::Character("c".into()),
D => Key::Character("d".into()),
E => Key::Character("e".into()),
F => Key::Character("f".into()),
G => Key::Character("g".into()),
H => Key::Character("h".into()),
I => Key::Character("i".into()),
J => Key::Character("j".into()),
K => Key::Character("k".into()),
L => Key::Character("l".into()),
M => Key::Character("m".into()),
N => Key::Character("n".into()),
O => Key::Character("o".into()),
P => Key::Character("p".into()),
Q => Key::Character("q".into()),
R => Key::Character("r".into()),
S => Key::Character("s".into()),
T => Key::Character("t".into()),
U => Key::Character("u".into()),
V => Key::Character("v".into()),
W => Key::Character("w".into()),
X => Key::Character("x".into()),
Y => Key::Character("y".into()),
Z => Key::Character("z".into()),
Comma => Key::Character(",".into()),
Period => Key::Character(".".into()),
Grave => Key::Character("`".into()),
Minus => Key::Character("-".into()),
Equals => Key::Character("=".into()),
LeftBracket => Key::Character("[".into()),
RightBracket => Key::Character("]".into()),
Backslash => Key::Character("\\".into()),
Semicolon => Key::Character(";".into()),
Apostrophe => Key::Character("'".into()),
Slash => Key::Character("/".into()),
At => Key::Character("@".into()),
Plus => Key::Character("+".into()),
//-------------------------------------------------------------------------------
DpadUp => Key::ArrowUp,
DpadDown => Key::ArrowDown,
DpadLeft => Key::ArrowLeft,
DpadRight => Key::ArrowRight,
DpadCenter => Key::Enter,
VolumeUp => Key::AudioVolumeUp,
VolumeDown => Key::AudioVolumeDown,
Power => Key::Power,
Camera => Key::Camera,
Clear => Key::Clear,
VolumeUp => Key::AudioVolumeUp,
VolumeDown => Key::AudioVolumeDown,
Power => Key::Power,
Camera => Key::Camera,
Clear => Key::Clear,
AltLeft => Key::Alt,
AltRight => Key::Alt,
ShiftLeft => Key::Shift,
ShiftRight => Key::Shift,
Tab => Key::Tab,
Space => Key::Space,
Sym => Key::Symbol,
Explorer => Key::LaunchWebBrowser,
Envelope => Key::LaunchMail,
Enter => Key::Enter,
Del => Key::Backspace,
AltLeft => Key::Alt,
AltRight => Key::Alt,
ShiftLeft => Key::Shift,
ShiftRight => Key::Shift,
Tab => Key::Tab,
Space => Key::Space,
Sym => Key::Symbol,
Explorer => Key::LaunchWebBrowser,
Envelope => Key::LaunchMail,
Enter => Key::Enter,
Del => Key::Backspace,
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => Key::Alt,
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => Key::Alt,
Headsethook => Key::HeadsetHook,
Focus => Key::CameraFocus,
Headsethook => Key::HeadsetHook,
Focus => Key::CameraFocus,
Notification => Key::Notification,
Search => Key::BrowserSearch,
MediaPlayPause => Key::MediaPlayPause,
MediaStop => Key::MediaStop,
MediaNext => Key::MediaTrackNext,
MediaPrevious => Key::MediaTrackPrevious,
MediaRewind => Key::MediaRewind,
MediaFastForward => Key::MediaFastForward,
Mute => Key::MicrophoneVolumeMute,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
Menu => Key::Unidentified(native),
Escape => Key::Escape,
ForwardDel => Key::Delete,
CtrlLeft => Key::Control,
CtrlRight => Key::Control,
CapsLock => Key::CapsLock,
ScrollLock => Key::ScrollLock,
MetaLeft => Key::Super,
MetaRight => Key::Super,
Function => Key::Fn,
Sysrq => Key::PrintScreen,
Break => Key::Pause,
MoveHome => Key::Home,
MoveEnd => Key::End,
Insert => Key::Insert,
Forward => Key::BrowserForward,
MediaPlay => Key::MediaPlay,
MediaPause => Key::MediaPause,
MediaClose => Key::MediaClose,
MediaEject => Key::Eject,
MediaRecord => Key::MediaRecord,
F1 => Key::F1,
F2 => Key::F2,
F3 => Key::F3,
F4 => Key::F4,
F5 => Key::F5,
F6 => Key::F6,
F7 => Key::F7,
F8 => Key::F8,
F9 => Key::F9,
F10 => Key::F10,
F11 => Key::F11,
F12 => Key::F12,
NumLock => Key::NumLock,
Numpad0 => Key::Character("0".into()),
Numpad1 => Key::Character("1".into()),
Numpad2 => Key::Character("2".into()),
Numpad3 => Key::Character("3".into()),
Numpad4 => Key::Character("4".into()),
Numpad5 => Key::Character("5".into()),
Numpad6 => Key::Character("6".into()),
Numpad7 => Key::Character("7".into()),
Numpad8 => Key::Character("8".into()),
Numpad9 => Key::Character("9".into()),
NumpadDivide => Key::Character("/".into()),
NumpadMultiply => Key::Character("*".into()),
NumpadSubtract => Key::Character("-".into()),
NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Enter,
NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()),
Notification => Key::Notification,
Search => Key::BrowserSearch,
MediaPlayPause => Key::MediaPlayPause,
MediaStop => Key::MediaStop,
MediaNext => Key::MediaTrackNext,
MediaPrevious => Key::MediaTrackPrevious,
MediaRewind => Key::MediaRewind,
MediaFastForward => Key::MediaFastForward,
Mute => Key::MicrophoneVolumeMute,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
Pictsymbols => Key::Unidentified(native),
SwitchCharset => Key::Unidentified(native),
VolumeMute => Key::AudioVolumeMute,
Info => Key::Info,
ChannelUp => Key::ChannelUp,
ChannelDown => Key::ChannelDown,
ZoomIn => Key::ZoomIn,
ZoomOut => Key::ZoomOut,
Tv => Key::TV,
Guide => Key::Guide,
Dvr => Key::DVR,
Bookmark => Key::BrowserFavorites,
Captions => Key::ClosedCaptionToggle,
Settings => Key::Settings,
TvPower => Key::TVPower,
TvInput => Key::TVInput,
StbPower => Key::STBPower,
StbInput => Key::STBInput,
AvrPower => Key::AVRPower,
AvrInput => Key::AVRInput,
ProgRed => Key::ColorF0Red,
ProgGreen => Key::ColorF1Green,
ProgYellow => Key::ColorF2Yellow,
ProgBlue => Key::ColorF3Blue,
AppSwitch => Key::AppSwitch,
LanguageSwitch => Key::GroupNext,
MannerMode => Key::MannerMode,
Keycode3dMode => Key::TV3DMode,
Contacts => Key::LaunchContacts,
Calendar => Key::LaunchCalendar,
Music => Key::LaunchMusicPlayer,
Calculator => Key::LaunchApplication2,
ZenkakuHankaku => Key::ZenkakuHankaku,
Eisu => Key::Eisu,
Muhenkan => Key::NonConvert,
Henkan => Key::Convert,
KatakanaHiragana => Key::HiraganaKatakana,
Kana => Key::KanjiMode,
BrightnessDown => Key::BrightnessDown,
BrightnessUp => Key::BrightnessUp,
MediaAudioTrack => Key::MediaAudioTrack,
Sleep => Key::Standby,
Wakeup => Key::WakeUp,
Pairing => Key::Pairing,
MediaTopMenu => Key::MediaTopMenu,
LastChannel => Key::MediaLast,
TvDataService => Key::TVDataService,
VoiceAssist => Key::VoiceDial,
TvRadioService => Key::TVRadioService,
TvTeletext => Key::Teletext,
TvNumberEntry => Key::TVNumberEntry,
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
TvTerrestrialDigital => Key::TVTerrestrialDigital,
TvSatellite => Key::TVSatellite,
TvSatelliteBs => Key::TVSatelliteBS,
TvSatelliteCs => Key::TVSatelliteCS,
TvSatelliteService => Key::TVSatelliteToggle,
TvNetwork => Key::TVNetwork,
TvAntennaCable => Key::TVAntennaCable,
TvInputHdmi1 => Key::TVInputHDMI1,
TvInputHdmi2 => Key::TVInputHDMI2,
TvInputHdmi3 => Key::TVInputHDMI3,
TvInputHdmi4 => Key::TVInputHDMI4,
TvInputComposite1 => Key::TVInputComposite1,
TvInputComposite2 => Key::TVInputComposite2,
TvInputComponent1 => Key::TVInputComponent1,
TvInputComponent2 => Key::TVInputComponent2,
TvInputVga1 => Key::TVInputVGA1,
TvAudioDescription => Key::TVAudioDescription,
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
TvZoomMode => Key::ZoomToggle,
TvContentsMenu => Key::TVContentsMenu,
TvMediaContextMenu => Key::TVMediaContext,
TvTimerProgramming => Key::TVTimer,
Help => Key::Help,
NavigatePrevious => Key::NavigatePrevious,
NavigateNext => Key::NavigateNext,
NavigateIn => Key::NavigateIn,
NavigateOut => Key::NavigateOut,
MediaSkipForward => Key::MediaSkipForward,
MediaSkipBackward => Key::MediaSkipBackward,
MediaStepForward => Key::MediaStepForward,
MediaStepBackward => Key::MediaStepBackward,
Cut => Key::Cut,
Copy => Key::Copy,
Paste => Key::Paste,
Refresh => Key::BrowserRefresh,
// -----------------------------------------------------------------
// Gamepad events should be exposed through a separate API, not
// keyboard events
ButtonA => Key::Unidentified(native),
ButtonB => Key::Unidentified(native),
ButtonC => Key::Unidentified(native),
ButtonX => Key::Unidentified(native),
ButtonY => Key::Unidentified(native),
ButtonZ => Key::Unidentified(native),
ButtonL1 => Key::Unidentified(native),
ButtonR1 => Key::Unidentified(native),
ButtonL2 => Key::Unidentified(native),
ButtonR2 => Key::Unidentified(native),
ButtonThumbl => Key::Unidentified(native),
ButtonThumbr => Key::Unidentified(native),
ButtonStart => Key::Unidentified(native),
ButtonSelect => Key::Unidentified(native),
ButtonMode => Key::Unidentified(native),
// -----------------------------------------------------------------
Escape => Key::Escape,
ForwardDel => Key::Delete,
CtrlLeft => Key::Control,
CtrlRight => Key::Control,
CapsLock => Key::CapsLock,
ScrollLock => Key::ScrollLock,
MetaLeft => Key::Super,
MetaRight => Key::Super,
Function => Key::Fn,
Sysrq => Key::PrintScreen,
Break => Key::Pause,
MoveHome => Key::Home,
MoveEnd => Key::End,
Insert => Key::Insert,
Forward => Key::BrowserForward,
MediaPlay => Key::MediaPlay,
MediaPause => Key::MediaPause,
MediaClose => Key::MediaClose,
MediaEject => Key::Eject,
MediaRecord => Key::MediaRecord,
F1 => Key::F1,
F2 => Key::F2,
F3 => Key::F3,
F4 => Key::F4,
F5 => Key::F5,
F6 => Key::F6,
F7 => Key::F7,
F8 => Key::F8,
F9 => Key::F9,
F10 => Key::F10,
F11 => Key::F11,
F12 => Key::F12,
NumLock => Key::NumLock,
Numpad0 => Key::Character("0".into()),
Numpad1 => Key::Character("1".into()),
Numpad2 => Key::Character("2".into()),
Numpad3 => Key::Character("3".into()),
Numpad4 => Key::Character("4".into()),
Numpad5 => Key::Character("5".into()),
Numpad6 => Key::Character("6".into()),
Numpad7 => Key::Character("7".into()),
Numpad8 => Key::Character("8".into()),
Numpad9 => Key::Character("9".into()),
NumpadDivide => Key::Character("/".into()),
NumpadMultiply => Key::Character("*".into()),
NumpadSubtract => Key::Character("-".into()),
NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Enter,
NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()),
// -----------------------------------------------------------------
// Keycodes that don't have a logical Key mapping
// -----------------------------------------------------------------
Unknown => Key::Unidentified(native),
VolumeMute => Key::AudioVolumeMute,
Info => Key::Info,
ChannelUp => Key::ChannelUp,
ChannelDown => Key::ChannelDown,
ZoomIn => Key::ZoomIn,
ZoomOut => Key::ZoomOut,
Tv => Key::TV,
Window => Key::Unidentified(native),
Guide => Key::Guide,
Dvr => Key::DVR,
Bookmark => Key::BrowserFavorites,
Captions => Key::ClosedCaptionToggle,
Settings => Key::Settings,
TvPower => Key::TVPower,
TvInput => Key::TVInput,
StbPower => Key::STBPower,
StbInput => Key::STBInput,
AvrPower => Key::AVRPower,
AvrInput => Key::AVRInput,
ProgRed => Key::ColorF0Red,
ProgGreen => Key::ColorF1Green,
ProgYellow => Key::ColorF2Yellow,
ProgBlue => Key::ColorF3Blue,
AppSwitch => Key::AppSwitch,
Button1 => Key::Unidentified(native),
Button2 => Key::Unidentified(native),
Button3 => Key::Unidentified(native),
Button4 => Key::Unidentified(native),
Button5 => Key::Unidentified(native),
Button6 => Key::Unidentified(native),
Button7 => Key::Unidentified(native),
Button8 => Key::Unidentified(native),
Button9 => Key::Unidentified(native),
Button10 => Key::Unidentified(native),
Button11 => Key::Unidentified(native),
Button12 => Key::Unidentified(native),
Button13 => Key::Unidentified(native),
Button14 => Key::Unidentified(native),
Button15 => Key::Unidentified(native),
Button16 => Key::Unidentified(native),
LanguageSwitch => Key::GroupNext,
MannerMode => Key::MannerMode,
Keycode3dMode => Key::TV3DMode,
Contacts => Key::LaunchContacts,
Calendar => Key::LaunchCalendar,
Music => Key::LaunchMusicPlayer,
Calculator => Key::LaunchApplication2,
ZenkakuHankaku => Key::ZenkakuHankaku,
Eisu => Key::Eisu,
Muhenkan => Key::NonConvert,
Henkan => Key::Convert,
KatakanaHiragana => Key::HiraganaKatakana,
Yen => Key::Unidentified(native),
Ro => Key::Unidentified(native),
Kana => Key::KanjiMode,
Assist => Key::Unidentified(native),
BrightnessDown => Key::BrightnessDown,
BrightnessUp => Key::BrightnessUp,
MediaAudioTrack => Key::MediaAudioTrack,
Sleep => Key::Standby,
Wakeup => Key::WakeUp,
Pairing => Key::Pairing,
MediaTopMenu => Key::MediaTopMenu,
Keycode11 => Key::Unidentified(native),
Keycode12 => Key::Unidentified(native),
LastChannel => Key::MediaLast,
TvDataService => Key::TVDataService,
VoiceAssist => Key::VoiceDial,
TvRadioService => Key::TVRadioService,
TvTeletext => Key::Teletext,
TvNumberEntry => Key::TVNumberEntry,
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
TvTerrestrialDigital => Key::TVTerrestrialDigital,
TvSatellite => Key::TVSatellite,
TvSatelliteBs => Key::TVSatelliteBS,
TvSatelliteCs => Key::TVSatelliteCS,
TvSatelliteService => Key::TVSatelliteToggle,
TvNetwork => Key::TVNetwork,
TvAntennaCable => Key::TVAntennaCable,
TvInputHdmi1 => Key::TVInputHDMI1,
TvInputHdmi2 => Key::TVInputHDMI2,
TvInputHdmi3 => Key::TVInputHDMI3,
TvInputHdmi4 => Key::TVInputHDMI4,
TvInputComposite1 => Key::TVInputComposite1,
TvInputComposite2 => Key::TVInputComposite2,
TvInputComponent1 => Key::TVInputComponent1,
TvInputComponent2 => Key::TVInputComponent2,
TvInputVga1 => Key::TVInputVGA1,
TvAudioDescription => Key::TVAudioDescription,
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
TvZoomMode => Key::ZoomToggle,
TvContentsMenu => Key::TVContentsMenu,
TvMediaContextMenu => Key::TVMediaContext,
TvTimerProgramming => Key::TVTimer,
Help => Key::Help,
NavigatePrevious => Key::NavigatePrevious,
NavigateNext => Key::NavigateNext,
NavigateIn => Key::NavigateIn,
NavigateOut => Key::NavigateOut,
StemPrimary => Key::Unidentified(native),
Stem1 => Key::Unidentified(native),
Stem2 => Key::Unidentified(native),
Stem3 => Key::Unidentified(native),
DpadUpLeft => Key::Unidentified(native),
DpadDownLeft => Key::Unidentified(native),
DpadUpRight => Key::Unidentified(native),
DpadDownRight => Key::Unidentified(native),
MediaSkipForward => Key::MediaSkipForward,
MediaSkipBackward => Key::MediaSkipBackward,
MediaStepForward => Key::MediaStepForward,
MediaStepBackward => Key::MediaStepBackward,
SoftSleep => Key::Unidentified(native),
Cut => Key::Cut,
Copy => Key::Copy,
Paste => Key::Paste,
SystemNavigationUp => Key::Unidentified(native),
SystemNavigationDown => Key::Unidentified(native),
SystemNavigationLeft => Key::Unidentified(native),
SystemNavigationRight => Key::Unidentified(native),
AllApps => Key::Unidentified(native),
Refresh => Key::BrowserRefresh,
ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native),
// Can be added on demand
SoftLeft => Key::Unidentified(native),
SoftRight => Key::Unidentified(native),
Menu => Key::Unidentified(native),
Pictsymbols => Key::Unidentified(native),
SwitchCharset => Key::Unidentified(native),
// -----------------------------------------------------------------
// Gamepad events should be exposed through a separate API, not
// keyboard events
ButtonA => Key::Unidentified(native),
ButtonB => Key::Unidentified(native),
ButtonC => Key::Unidentified(native),
ButtonX => Key::Unidentified(native),
ButtonY => Key::Unidentified(native),
ButtonZ => Key::Unidentified(native),
ButtonL1 => Key::Unidentified(native),
ButtonR1 => Key::Unidentified(native),
ButtonL2 => Key::Unidentified(native),
ButtonR2 => Key::Unidentified(native),
ButtonThumbl => Key::Unidentified(native),
ButtonThumbr => Key::Unidentified(native),
ButtonStart => Key::Unidentified(native),
ButtonSelect => Key::Unidentified(native),
ButtonMode => Key::Unidentified(native),
// -----------------------------------------------------------------
Window => Key::Unidentified(native),
Button1 => Key::Unidentified(native),
Button2 => Key::Unidentified(native),
Button3 => Key::Unidentified(native),
Button4 => Key::Unidentified(native),
Button5 => Key::Unidentified(native),
Button6 => Key::Unidentified(native),
Button7 => Key::Unidentified(native),
Button8 => Key::Unidentified(native),
Button9 => Key::Unidentified(native),
Button10 => Key::Unidentified(native),
Button11 => Key::Unidentified(native),
Button12 => Key::Unidentified(native),
Button13 => Key::Unidentified(native),
Button14 => Key::Unidentified(native),
Button15 => Key::Unidentified(native),
Button16 => Key::Unidentified(native),
Yen => Key::Unidentified(native),
Ro => Key::Unidentified(native),
Assist => Key::Unidentified(native),
Keycode11 => Key::Unidentified(native),
Keycode12 => Key::Unidentified(native),
StemPrimary => Key::Unidentified(native),
Stem1 => Key::Unidentified(native),
Stem2 => Key::Unidentified(native),
Stem3 => Key::Unidentified(native),
DpadUpLeft => Key::Unidentified(native),
DpadDownLeft => Key::Unidentified(native),
DpadUpRight => Key::Unidentified(native),
DpadDownRight => Key::Unidentified(native),
SoftSleep => Key::Unidentified(native),
SystemNavigationUp => Key::Unidentified(native),
SystemNavigationDown => Key::Unidentified(native),
SystemNavigationLeft => Key::Unidentified(native),
SystemNavigationRight => Key::Unidentified(native),
AllApps => Key::Unidentified(native),
ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native),
},
}
}

View File

@@ -5,7 +5,7 @@ use std::{
hash::Hash,
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc, RwLock,
mpsc, Arc, Mutex, RwLock,
},
time::{Duration, Instant},
};
@@ -19,22 +19,31 @@ use raw_window_handle::{
AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use crate::platform_impl::Fullscreen;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, StartCause},
event::{self, InnerSizeWriter, StartCause},
event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW},
keyboard::NativeKey,
platform::pump_events::PumpStatus,
window::{
self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel,
},
};
use crate::{error::EventLoopError, platform_impl::Fullscreen};
mod keycodes;
static HAS_FOCUS: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| {
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
})
}
struct PeekableReceiver<T> {
recv: mpsc::Receiver<T>,
first: Option<T>,
@@ -135,8 +144,13 @@ pub struct EventLoop<T: 'static> {
redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool,
pending_redraw: bool,
control_flow: ControlFlow,
cause: StartCause,
ignore_volume_keys: bool,
combining_accent: Option<char>,
}
#[derive(Debug, Clone, PartialEq)]
@@ -155,12 +169,12 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
fn sticky_exit_callback<T, F>(
evt: event::Event<'_, T>,
evt: event::Event<T>,
target: &RootELW<T>,
control_flow: &mut ControlFlow,
callback: &mut F,
) where
F: FnMut(event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
@@ -171,20 +185,16 @@ fn sticky_exit_callback<T, F>(
}
}
struct IterationResult {
deadline: Option<Instant>,
timeout: Option<Duration>,
wait_start: Instant,
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let (user_events_sender, user_events_receiver) = mpsc::channel();
let android_app = attributes.android_app.as_ref().expect("An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on Android");
let redraw_flag = SharedFlag::new();
Self {
Ok(Self {
android_app: android_app.clone(),
window_target: event_loop::EventLoopWindowTarget {
p: EventLoopWindowTarget {
@@ -200,33 +210,34 @@ impl<T: 'static> EventLoop<T> {
redraw_flag,
user_events_sender,
user_events_receiver: PeekableReceiver::from_recv(user_events_receiver),
loop_running: false,
running: false,
pending_redraw: false,
control_flow: Default::default(),
cause: StartCause::Init,
ignore_volume_keys: attributes.ignore_volume_keys,
}
combining_accent: None,
})
}
fn single_iteration<F>(
&mut self,
control_flow: &mut ControlFlow,
main_event: Option<MainEvent<'_>>,
pending_redraw: &mut bool,
cause: &mut StartCause,
callback: &mut F,
) -> IterationResult
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where
F: FnMut(event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
trace!("Mainloop iteration");
let cause = self.cause;
let mut control_flow = self.control_flow;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
sticky_exit_callback(
event::Event::NewEvents(*cause),
event::Event::NewEvents(cause),
self.window_target(),
control_flow,
&mut control_flow,
callback,
);
let mut resized = false;
if let Some(event) = main_event {
trace!("Handling main event {:?}", event);
@@ -235,7 +246,7 @@ impl<T: 'static> EventLoop<T> {
sticky_exit_callback(
event::Event::Resumed,
self.window_target(),
control_flow,
&mut control_flow,
callback,
);
}
@@ -243,12 +254,12 @@ impl<T: 'static> EventLoop<T> {
sticky_exit_callback(
event::Event::Suspended,
self.window_target(),
control_flow,
&mut control_flow,
callback,
);
}
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => *pending_redraw = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
MainEvent::ContentRectChanged { .. } => {
warn!("TODO: find a way to notify application of content rect change");
}
@@ -260,7 +271,7 @@ impl<T: 'static> EventLoop<T> {
event: event::WindowEvent::Focused(true),
},
self.window_target(),
control_flow,
&mut control_flow,
callback,
);
}
@@ -272,7 +283,7 @@ impl<T: 'static> EventLoop<T> {
event: event::WindowEvent::Focused(false),
},
self.window_target(),
control_flow,
&mut control_flow,
callback,
);
}
@@ -281,15 +292,24 @@ impl<T: 'static> EventLoop<T> {
let old_scale_factor = monitor.scale_factor();
let scale_factor = monitor.scale_factor();
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
let mut size = MonitorHandle::new(self.android_app.clone()).size();
let new_inner_size = Arc::new(Mutex::new(
MonitorHandle::new(self.android_app.clone()).size(),
));
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::ScaleFactorChanged {
new_inner_size: &mut size,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
},
};
sticky_exit_callback(event, self.window_target(), control_flow, callback);
sticky_exit_callback(
event,
self.window_target(),
&mut control_flow,
callback,
);
}
}
MainEvent::LowMemory => {
@@ -336,128 +356,25 @@ impl<T: 'static> EventLoop<T> {
trace!("No main event to handle");
}
// temporarily decouple `android_app` from `self` so we aren't holding
// a borrow of `self` while iterating
let android_app = self.android_app.clone();
// Process input events
self.android_app.input_events(|event| {
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
let window_id = window::WindowId(WindowId);
let device_id = event::DeviceId(DeviceId);
match android_app.input_events_iter() {
Ok(mut input_iter) => loop {
let read_event = input_iter.next(|event| {
self.handle_input_event(&android_app, event, &mut control_flow, callback)
});
let phase = match motion_event.action() {
MotionAction::Down | MotionAction::PointerDown => {
Some(event::TouchPhase::Started)
}
MotionAction::Up | MotionAction::PointerUp => {
Some(event::TouchPhase::Ended)
}
MotionAction::Move => Some(event::TouchPhase::Moved),
MotionAction::Cancel => {
Some(event::TouchPhase::Cancelled)
}
_ => {
None // TODO mouse events
}
};
if let Some(phase) = phase {
let pointers: Box<
dyn Iterator<Item = android_activity::input::Pointer<'_>>,
> = match phase {
event::TouchPhase::Started
| event::TouchPhase::Ended => {
Box::new(
std::iter::once(motion_event.pointer_at_index(
motion_event.pointer_index(),
))
)
},
event::TouchPhase::Moved
| event::TouchPhase::Cancelled => {
Box::new(motion_event.pointers())
}
};
for pointer in pointers {
let location = PhysicalPosition {
x: pointer.x() as _,
y: pointer.y() as _,
};
trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}");
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(
event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: None,
},
),
};
sticky_exit_callback(
event,
self.window_target(),
control_flow,
callback
);
}
}
}
InputEvent::KeyEvent(key) => {
match key.key_code() {
// Flag keys related to volume as unhandled. While winit does not have a way for applications
// to configure what keys to flag as handled, this appears to be a good default until winit
// can be configured.
Keycode::VolumeUp |
Keycode::VolumeDown |
Keycode::VolumeMute => {
if self.ignore_volume_keys {
input_status = InputStatus::Unhandled
}
},
keycode => {
let state = match key.action() {
KeyAction::Down => event::ElementState::Pressed,
KeyAction::Up => event::ElementState::Released,
_ => event::ElementState::Released,
};
let native = NativeKey::Android(keycode.into());
let logical_key = keycodes::to_logical(keycode, native);
// TODO: maybe use getUnicodeChar to get the logical key
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_keycode(keycode),
logical_key,
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
};
sticky_exit_callback(
event,
self.window_target(),
control_flow,
callback,
);
}
}
}
_ => {
warn!("Unknown android_activity input event {event:?}")
if !read_event {
break;
}
},
Err(err) => {
log::warn!("Failed to get input events iterator: {err:?}");
}
input_status
});
}
// Empty the user event buffer
{
@@ -465,19 +382,12 @@ impl<T: 'static> EventLoop<T> {
sticky_exit_callback(
crate::event::Event::UserEvent(event),
self.window_target(),
control_flow,
&mut control_flow,
callback,
);
}
}
sticky_exit_callback(
event::Event::MainEventsCleared,
self.window_target(),
control_flow,
callback,
);
if self.running {
if resized {
let size = if let Some(native_window) = self.android_app.native_window().as_ref() {
@@ -491,163 +401,290 @@ impl<T: 'static> EventLoop<T> {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Resized(size),
};
sticky_exit_callback(event, self.window_target(), control_flow, callback);
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
}
*pending_redraw |= self.redraw_flag.get_and_reset();
if *pending_redraw {
*pending_redraw = false;
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
sticky_exit_callback(event, self.window_target(), control_flow, callback);
sticky_exit_callback(event, self.window_target(), &mut control_flow, callback);
}
}
// This is always the last event we dispatch before poll again
sticky_exit_callback(
event::Event::RedrawEventsCleared,
event::Event::AboutToWait,
self.window_target(),
control_flow,
&mut control_flow,
callback,
);
let start = Instant::now();
let (deadline, timeout);
match control_flow {
ControlFlow::ExitWithCode(_) => {
deadline = None;
timeout = None;
}
ControlFlow::Poll => {
*cause = StartCause::Poll;
deadline = None;
timeout = Some(Duration::from_millis(0));
}
ControlFlow::Wait => {
*cause = StartCause::WaitCancelled {
start,
requested_resume: None,
};
deadline = None;
timeout = None;
}
ControlFlow::WaitUntil(wait_deadline) => {
*cause = StartCause::ResumeTimeReached {
start,
requested_resume: *wait_deadline,
};
timeout = if *wait_deadline > start {
Some(*wait_deadline - start)
} else {
Some(Duration::from_millis(0))
};
deadline = Some(*wait_deadline);
}
}
IterationResult {
wait_start: start,
deadline,
timeout,
}
self.control_flow = control_flow;
self.pending_redraw = pending_redraw;
}
pub fn run<F>(mut self, event_handler: F) -> !
fn handle_input_event<F>(
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
control_flow: &mut ControlFlow,
callback: &mut F,
) -> InputStatus
where
F: 'static
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
let exit_code = self.run_return(event_handler);
::std::process::exit(exit_code);
}
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
let window_id = window::WindowId(WindowId);
let device_id = event::DeviceId(DeviceId);
pub fn run_return<F>(&mut self, mut callback: F) -> i32
where
F: FnMut(event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::default();
let mut cause = StartCause::Init;
let mut pending_redraw = false;
let phase = match motion_event.action() {
MotionAction::Down | MotionAction::PointerDown => {
Some(event::TouchPhase::Started)
}
MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended),
MotionAction::Move => Some(event::TouchPhase::Moved),
MotionAction::Cancel => Some(event::TouchPhase::Cancelled),
_ => {
None // TODO mouse events
}
};
if let Some(phase) = phase {
let pointers: Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>> =
match phase {
event::TouchPhase::Started | event::TouchPhase::Ended => {
Box::new(std::iter::once(
motion_event.pointer_at_index(motion_event.pointer_index()),
))
}
event::TouchPhase::Moved | event::TouchPhase::Cancelled => {
Box::new(motion_event.pointers())
}
};
// run the initial loop iteration
let mut iter_result = self.single_iteration(
&mut control_flow,
None,
&mut pending_redraw,
&mut cause,
&mut callback,
);
let exit_code = loop {
if let ControlFlow::ExitWithCode(code) = control_flow {
break code;
for pointer in pointers {
let location = PhysicalPosition {
x: pointer.x() as _,
y: pointer.y() as _,
};
trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}");
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: None,
}),
};
sticky_exit_callback(event, self.window_target(), control_flow, callback);
}
}
}
let mut timeout = iter_result.timeout;
// If we already have work to do then we don't want to block on the next poll...
pending_redraw |= self.redraw_flag.get_and_reset();
if self.running && (pending_redraw || self.user_events_receiver.has_incoming()) {
timeout = Some(Duration::from_millis(0))
}
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
app.poll_events(timeout, |poll_event| {
let mut main_event = None;
match poll_event {
android_activity::PollEvent::Wake => {
// In the X11 backend it's noted that too many false-positive wake ups
// would cause the event loop to run continuously. They handle this by re-checking
// for pending events (assuming they cover all valid reasons for a wake up).
//
// For now, user_events and redraw_requests are the only reasons to expect
// a wake up here so we can ignore the wake up if there are no events/requests.
// We also ignore wake ups while suspended.
pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running
|| (!pending_redraw && !self.user_events_receiver.has_incoming())
{
return;
InputEvent::KeyEvent(key) => {
match key.key_code() {
// Flag keys related to volume as unhandled. While winit does not have a way for applications
// to configure what keys to flag as handled, this appears to be a good default until winit
// can be configured.
Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute => {
if self.ignore_volume_keys {
input_status = InputStatus::Unhandled
}
}
android_activity::PollEvent::Timeout => {}
android_activity::PollEvent::Main(event) => {
main_event = Some(event);
}
unknown_event => {
warn!("Unknown poll event {unknown_event:?} (ignored)");
keycode => {
let state = match key.action() {
KeyAction::Down => event::ElementState::Pressed,
KeyAction::Up => event::ElementState::Released,
_ => event::ElementState::Released,
};
let key_char = keycodes::character_map_and_combine_key(
android_app,
key,
&mut self.combining_accent,
);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_keycode(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
};
sticky_exit_callback(event, self.window_target(), control_flow, callback);
}
}
}
_ => {
warn!("Unknown android_activity input event {event:?}")
}
}
let wait_cancelled = iter_result
.deadline
.map_or(false, |deadline| Instant::now() < deadline);
input_status
}
if wait_cancelled {
cause = StartCause::WaitCancelled {
start: iter_result.wait_start,
requested_resume: iter_result.deadline,
};
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{
self.run_ondemand(event_handler)
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
}
loop {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
}
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
}
_ => {
continue;
}
}
}
}
iter_result = self.single_iteration(
&mut control_flow,
main_event,
&mut pending_redraw,
&mut cause,
&mut callback,
);
});
};
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
if !self.loop_running {
self.loop_running = true;
sticky_exit_callback(
event::Event::LoopDestroyed,
self.window_target(),
&mut control_flow,
&mut callback,
);
// Reset the internal state for the loop as we start running to
// ensure consistent behaviour in case the loop runs and exits more
// than once
self.pending_redraw = false;
self.cause = StartCause::Init;
self.control_flow = ControlFlow::Poll;
exit_code
// run the initial loop iteration
self.single_iteration(None, &mut callback);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
self.poll_events_with_timeout(timeout, &mut callback);
}
if let ControlFlow::ExitWithCode(code) = self.control_flow {
self.loop_running = false;
let mut dummy = self.control_flow;
sticky_exit_callback(
event::Event::LoopExiting,
self.window_target(),
&mut dummy,
&mut callback,
);
PumpStatus::Exit(code)
} else {
PumpStatus::Continue
}
}
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset();
timeout =
if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) {
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
}
// `ExitWithCode()` will be reset to `Poll` before polling
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
min_timeout(control_flow_timeout, timeout)
};
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
app.poll_events(timeout, |poll_event| {
let mut main_event = None;
match poll_event {
android_activity::PollEvent::Wake => {
// In the X11 backend it's noted that too many false-positive wake ups
// would cause the event loop to run continuously. They handle this by re-checking
// for pending events (assuming they cover all valid reasons for a wake up).
//
// For now, user_events and redraw_requests are the only reasons to expect
// a wake up here so we can ignore the wake up if there are no events/requests.
// We also ignore wake ups while suspended.
self.pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running
|| (!self.pending_redraw && !self.user_events_receiver.has_incoming())
{
return;
}
}
android_activity::PollEvent::Timeout => {}
android_activity::PollEvent::Main(event) => {
main_event = Some(event);
}
unknown_event => {
warn!("Unknown poll event {unknown_event:?} (ignored)");
}
}
self.cause = match self.control_flow {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
requested_resume: None,
},
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}
} else {
StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}
}
}
// `ExitWithCode()` will be reset to `Poll` before polling
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
self.single_iteration(main_event, &mut callback);
});
}
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
@@ -760,6 +797,14 @@ impl Window {
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
pub fn id(&self) -> WindowId {
WindowId
}
@@ -786,6 +831,8 @@ impl Window {
self.redraw_requester.request_redraw()
}
pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
}
@@ -802,8 +849,8 @@ impl Window {
self.outer_size()
}
pub fn set_inner_size(&self, _size: Size) {
warn!("Cannot set window size on Android");
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.inner_size())
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
@@ -973,8 +1020,8 @@ pub struct MonitorHandle {
app: AndroidApp,
}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
Some(std::cmp::Ordering::Equal)
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MonitorHandle {

View File

@@ -6,6 +6,7 @@ use std::{
mem,
os::raw::c_void,
ptr,
sync::{Arc, Mutex},
time::Instant,
};
@@ -15,9 +16,9 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::foundation::{CGRect, CGSize, NSInteger, NSProcessInfo};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use icrate::Foundation::{CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSProcessInfo};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel};
use once_cell::sync::Lazy;
@@ -25,12 +26,9 @@ use super::uikit::UIView;
use super::view::WinitUIWindow;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
ffi::NSOperatingSystemVersion,
},
platform_impl::platform::event_loop::{EventHandler, EventProxy, EventWrapper, Never},
window::WindowId as RootWindowId,
};
@@ -57,7 +55,7 @@ enum UserCallbackTransitionResult<'a> {
},
}
impl Event<'static, Never> {
impl Event<Never> {
fn is_redraw(&self) -> bool {
matches!(self, Event::RedrawRequested(_))
}
@@ -68,25 +66,25 @@ impl Event<'static, Never> {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_windows: Vec<Id<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
Launching {
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_windows: Vec<Id<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
ProcessingEvents {
event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
ProcessingRedraws {
event_handler: Box<dyn EventHandler>,
@@ -204,9 +202,7 @@ impl AppState {
});
}
fn did_finish_launching_transition(
&mut self,
) -> (Vec<Id<WinitUIWindow, Shared>>, Vec<EventWrapper>) {
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
@@ -363,7 +359,7 @@ impl AppState {
}
}
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow, Shared>> {
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents {
event_handler,
@@ -393,9 +389,6 @@ impl AppState {
let new = self.control_flow;
match (old, new) {
(ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
}),
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting {
@@ -428,6 +421,7 @@ impl AppState {
});
self.waker.start_at(new_instant)
}
// Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
@@ -446,17 +440,14 @@ impl AppState {
fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
s => bug!(
"`LoopDestroyed` happened while not processing events {:?}",
s
),
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
}
}
}
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
let mut this = AppState::get_mut();
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
@@ -479,7 +470,7 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
let mut this = AppState::get_mut();
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
@@ -544,7 +535,7 @@ pub unsafe fn did_finish_launching() {
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
let _: () = msg_send![&window, setScreen: ptr::null::<Object>()];
let _: () = msg_send![&window, setScreen: ptr::null::<AnyObject>()];
window.setScreen(&screen);
let controller = window.rootViewController();
@@ -758,21 +749,18 @@ pub unsafe fn handle_main_events_cleared() {
};
drop(this);
// User events are always sent out at the end of the "MainEventLoop"
handle_user_events();
handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
let mut this = AppState::get_mut();
let mut redraw_events: Vec<EventWrapper> = this
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
.collect();
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
drop(this);
handle_nonuser_events(redraw_events);
handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait));
}
// requires main thread
@@ -787,7 +775,7 @@ pub unsafe fn terminated() {
let mut control_flow = this.control_flow;
drop(this);
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow)
}
fn handle_event_proxy(
@@ -815,27 +803,27 @@ fn handle_hidpi_proxy(
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window: Id<WinitUIWindow, Shared>,
window: Id<WinitUIWindow>,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor)));
let event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
event_handler.handle_nonuser_event(event, &mut control_flow);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size;
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size.width, logical_size.height);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
view.setFrame(new_frame);
}
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView, Shared>, CGRect) {
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView>, CGRect) {
let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap();
let bounds = window.bounds();
@@ -924,7 +912,7 @@ macro_rules! os_capabilities {
impl From<NSOperatingSystemVersion> for OSCapabilities {
fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities {
$(let $name = os_version.meets_requirements($major, $minor);)*
$(let $name = meets_requirements(os_version, $major, $minor);)*
OSCapabilities { $($name,)* os_version, }
}
}
@@ -934,7 +922,7 @@ macro_rules! os_capabilities {
pub fn $error_name(&self, extra_msg: &str) {
log::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch,
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
)
}
@@ -962,16 +950,18 @@ os_capabilities! {
force_touch: 9-0,
}
impl NSOperatingSystemVersion {
fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool {
(self.major, self.minor) >= (required_major, required_minor)
}
fn meets_requirements(
version: NSOperatingSystemVersion,
required_major: NSInteger,
required_minor: NSInteger,
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
pub fn os_capabilities() -> OSCapabilities {
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
let version: NSOperatingSystemVersion = unsafe {
let process_info = NSProcessInfo::process_info();
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8: bool = msg_send![
&process_info,
respondsToSelector: sel!(operatingSystemVersion)
@@ -984,7 +974,7 @@ pub fn os_capabilities() -> OSCapabilities {
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
msg_send![&process_info, operatingSystemVersion]
process_info.operatingSystemVersion()
};
version.into()
});

View File

@@ -14,16 +14,14 @@ use core_foundation::runloop::{
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::foundation::{MainThreadMarker, NSString};
use objc2::rc::{Id, Shared};
use icrate::Foundation::{MainThreadMarker, NSString};
use objc2::rc::Id;
use objc2::ClassType;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle};
use crate::{
dpi::LogicalSize,
error::EventLoopError,
event::Event,
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
@@ -31,16 +29,20 @@ use crate::{
platform::ios::Idiom,
};
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle};
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<'static, Never>),
StaticEvent(Event<Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitUIWindow, Shared>,
window: Id<WinitUIWindow>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
@@ -75,7 +77,9 @@ pub struct EventLoop<T: 'static> {
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, EventLoopError> {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
static mut SINGLETON_INIT: bool = false;
@@ -93,7 +97,7 @@ impl<T: 'static> EventLoop<T> {
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
EventLoop {
Ok(EventLoop {
window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget {
receiver,
@@ -101,12 +105,12 @@ impl<T: 'static> EventLoop<T> {
},
_marker: PhantomData,
},
}
})
}
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let application = UIApplication::shared(MainThreadMarker::new().unwrap());
@@ -116,10 +120,18 @@ impl<T: 'static> EventLoop<T> {
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
);
app_state::will_launch(Box::new(EventLoopHandler {
let event_handler = std::mem::transmute::<
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow)>,
Box<EventHandlerCallback<T>>,
>(Box::new(event_handler));
let handler = EventLoopHandler {
f: event_handler,
event_loop: self.window_target,
}));
};
app_state::will_launch(Box::new(handler));
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
@@ -237,10 +249,10 @@ fn setup_control_flow_observers() {
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
@@ -314,17 +326,20 @@ fn setup_control_flow_observers() {
#[derive(Debug)]
pub enum Never {}
type EventHandlerCallback<T> =
dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static;
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
struct EventLoopHandler<F, T: 'static> {
f: F,
struct EventLoopHandler<T: 'static> {
f: Box<EventHandlerCallback<T>>,
event_loop: RootEventLoopWindowTarget<T>,
}
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
impl<T: 'static> Debug for EventLoopHandler<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("event_loop", &self.event_loop)
@@ -332,12 +347,8 @@ impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
}
}
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
impl<T: 'static> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,

View File

@@ -2,30 +2,11 @@
use std::convert::TryInto;
use icrate::Foundation::{NSInteger, NSUInteger};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSUInteger};
use crate::platform::ios::{Idiom, ScreenEdge};
#[repr(C)]
#[derive(Clone, Debug)]
pub struct NSOperatingSystemVersion {
pub major: NSInteger,
pub minor: NSInteger,
pub patch: NSInteger,
}
unsafe impl Encode for NSOperatingSystemVersion {
const ENCODING: Encoding = Encoding::Struct(
"NSOperatingSystemVersion",
&[
NSInteger::ENCODING,
NSInteger::ENCODING,
NSInteger::ENCODING,
],
);
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger);
@@ -70,6 +51,10 @@ impl From<UIUserInterfaceIdiom> for Idiom {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
impl UIRectEdge {
pub(crate) const NONE: Self = Self(0);
}
unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -48,12 +48,12 @@
//!
//! - applicationDidBecomeActive is Resumed
//! - applicationWillResignActive is Suspended
//! - applicationWillTerminate is LoopDestroyed
//! - applicationWillTerminate is LoopExiting
//!
//! Keep in mind that after LoopDestroyed event is received every attempt to draw with
//! Keep in mind that after LoopExiting event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
@@ -63,7 +63,7 @@
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
if !::objc2::foundation::is_main_thread() {
if !::icrate::Foundation::is_main_thread() {
panic!($($t)*);
}
};
@@ -89,7 +89,7 @@ pub(crate) use self::{
use self::uikit::UIScreen;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
pub(crate) use crate::platform_impl::Fullscreen;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {

View File

@@ -6,8 +6,8 @@ use std::{
ops::{Deref, DerefMut},
};
use objc2::foundation::{MainThreadMarker, NSInteger};
use objc2::rc::{Id, Shared};
use icrate::Foundation::{MainThreadMarker, NSInteger};
use objc2::rc::Id;
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
@@ -18,7 +18,7 @@ use crate::{
// TODO(madsmtm): Remove or refactor this
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode, Shared>);
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
@@ -33,7 +33,7 @@ pub struct VideoMode {
}
impl VideoMode {
fn new(uiscreen: Id<UIScreen, Shared>, screen_mode: Id<UIScreenMode, Shared>) -> VideoMode {
fn new(uiscreen: Id<UIScreen>, screen_mode: Id<UIScreenMode>) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
@@ -65,7 +65,7 @@ impl VideoMode {
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner {
uiscreen: Id<UIScreen, Shared>,
uiscreen: Id<UIScreen>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
@@ -135,7 +135,7 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
pub(crate) fn new(uiscreen: Id<UIScreen>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
Self {
inner: Inner { uiscreen },
@@ -182,13 +182,8 @@ impl Inner {
.uiscreen
.availableModes()
.into_iter()
.map(|mode| {
let mode: *const UIScreenMode = mode;
let mode = unsafe { Id::retain(mode as *mut UIScreenMode).unwrap() };
RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
}
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
})
.collect();
@@ -222,7 +217,7 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
// MonitorHandleExtIOS
impl Inner {
pub(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
pub(crate) fn ui_screen(&self) -> &Id<UIScreen> {
&self.uiscreen
}
@@ -237,10 +232,6 @@ impl Inner {
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()
.map(|screen| {
let screen: *const UIScreen = screen;
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
MonitorHandle::new(screen)
})
.map(MonitorHandle::new)
.collect()
}

View File

@@ -1,6 +1,6 @@
use objc2::foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use icrate::Foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIWindow};
@@ -11,20 +11,21 @@ extern_class!(
unsafe impl ClassType for UIApplication {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIApplication {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self, Shared>> {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self>> {
unsafe { msg_send_id![Self::class(), sharedApplication] }
}
pub fn windows(&self) -> Id<NSArray<UIWindow, Shared>, Shared> {
pub fn windows(&self) -> Id<NSArray<UIWindow>> {
unsafe { msg_send_id![self, windows] }
}
#[sel(statusBarFrame)]
#[method(statusBarFrame)]
pub fn statusBarFrame(&self) -> CGRect;
}
);

View File

@@ -1,5 +1,5 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use icrate::Foundation::NSObject;
use objc2::{extern_class, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,5 +7,6 @@ extern_class!(
unsafe impl ClassType for UICoordinateSpace {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);

View File

@@ -1,6 +1,6 @@
use objc2::foundation::{MainThreadMarker, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use icrate::Foundation::{MainThreadMarker, NSObject};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::super::ffi::UIUserInterfaceIdiom;
@@ -10,16 +10,17 @@ extern_class!(
unsafe impl ClassType for UIDevice {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self, Shared> {
pub fn current(_mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![Self::class(), currentDevice] }
}
#[sel(userInterfaceIdiom)]
#[method(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
}
);

View File

@@ -1,5 +1,5 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use icrate::Foundation::NSObject;
use objc2::{extern_class, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,5 +7,6 @@ extern_class!(
unsafe impl ClassType for UIEvent {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);

View File

@@ -4,7 +4,7 @@
use std::os::raw::{c_char, c_int};
use objc2::foundation::NSString;
use icrate::Foundation::NSString;
mod application;
mod coordinate_space;

View File

@@ -1,5 +1,5 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use icrate::Foundation::NSObject;
use objc2::{extern_class, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,5 +7,6 @@ extern_class!(
unsafe impl ClassType for UIResponder {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIScreenMode};
@@ -11,53 +11,54 @@ extern_class!(
unsafe impl ClassType for UIScreen {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIScreen {
pub fn main(_mtm: MainThreadMarker) -> Id<Self, Shared> {
pub fn main(_mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![Self::class(), mainScreen] }
}
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self, Shared>, Shared> {
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self>> {
unsafe { msg_send_id![Self::class(), screens] }
}
#[sel(bounds)]
#[method(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(scale)]
#[method(scale)]
pub fn scale(&self) -> CGFloat;
#[sel(nativeBounds)]
#[method(nativeBounds)]
pub fn nativeBounds(&self) -> CGRect;
#[sel(nativeScale)]
#[method(nativeScale)]
pub fn nativeScale(&self) -> CGFloat;
#[sel(maximumFramesPerSecond)]
#[method(maximumFramesPerSecond)]
pub fn maximumFramesPerSecond(&self) -> NSInteger;
pub fn mirroredScreen(&self) -> Id<Self, Shared> {
pub fn mirroredScreen(&self) -> Id<Self> {
unsafe { msg_send_id![Self::class(), mirroredScreen] }
}
pub fn preferredMode(&self) -> Option<Id<UIScreenMode, Shared>> {
pub fn preferredMode(&self) -> Option<Id<UIScreenMode>> {
unsafe { msg_send_id![self, preferredMode] }
}
#[sel(setCurrentMode:)]
#[method(setCurrentMode:)]
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode, Shared>, Shared> {
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode>> {
unsafe { msg_send_id![self, availableModes] }
}
#[sel(setOverscanCompensation:)]
#[method(setOverscanCompensation:)]
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace, Shared> {
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace> {
unsafe { msg_send_id![self, coordinateSpace] }
}
}

View File

@@ -1,5 +1,5 @@
use objc2::foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use icrate::Foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,12 +7,13 @@ extern_class!(
unsafe impl ClassType for UIScreenMode {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIScreenMode {
#[sel(size)]
#[method(size)]
pub fn size(&self) -> CGSize;
}
);

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::UIView;
@@ -10,27 +10,28 @@ extern_class!(
unsafe impl ClassType for UITouch {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITouch {
#[sel(locationInView:)]
#[method(locationInView:)]
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
#[sel(type)]
#[method(type)]
pub fn type_(&self) -> UITouchType;
#[sel(force)]
#[method(force)]
pub fn force(&self) -> CGFloat;
#[sel(maximumPossibleForce)]
#[method(maximumPossibleForce)]
pub fn maximumPossibleForce(&self) -> CGFloat;
#[sel(altitudeAngle)]
#[method(altitudeAngle)]
pub fn altitudeAngle(&self) -> CGFloat;
#[sel(phase)]
#[method(phase)]
pub fn phase(&self) -> UITouchPhase;
}
);

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -8,12 +8,13 @@ extern_class!(
unsafe impl ClassType for UITraitCollection {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITraitCollection {
#[sel(forceTouchCapability)]
#[method(forceTouchCapability)]
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
}
);

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{CGFloat, CGRect, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIResponder, UIViewController};
@@ -12,57 +12,58 @@ extern_class!(
unsafe impl ClassType for UIView {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIView {
#[sel(bounds)]
#[method(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(setBounds:)]
#[method(setBounds:)]
pub fn setBounds(&self, value: CGRect);
#[sel(frame)]
#[method(frame)]
pub fn frame(&self) -> CGRect;
#[sel(setFrame:)]
#[method(setFrame:)]
pub fn setFrame(&self, value: CGRect);
#[sel(contentScaleFactor)]
#[method(contentScaleFactor)]
pub fn contentScaleFactor(&self) -> CGFloat;
#[sel(setContentScaleFactor:)]
#[method(setContentScaleFactor:)]
pub fn setContentScaleFactor(&self, val: CGFloat);
#[sel(setMultipleTouchEnabled:)]
#[method(setMultipleTouchEnabled:)]
pub fn setMultipleTouchEnabled(&self, val: bool);
pub fn rootViewController(&self) -> Option<Id<UIViewController, Shared>> {
pub fn rootViewController(&self) -> Option<Id<UIViewController>> {
unsafe { msg_send_id![self, rootViewController] }
}
#[sel(setRootViewController:)]
#[method(setRootViewController:)]
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
#[sel(convertRect:toCoordinateSpace:)]
#[method(convertRect:toCoordinateSpace:)]
pub fn convertRect_toCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(convertRect:fromCoordinateSpace:)]
#[method(convertRect:fromCoordinateSpace:)]
pub fn convertRect_fromCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(safeAreaInsets)]
#[method(safeAreaInsets)]
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
#[sel(setNeedsDisplay)]
#[method(setNeedsDisplay)]
pub fn setNeedsDisplay(&self);
}
);

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{NSObject, NSUInteger};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSObject, NSUInteger};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIView};
@@ -12,28 +12,29 @@ extern_class!(
unsafe impl ClassType for UIViewController {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIViewController {
#[sel(attemptRotationToDeviceOrientation)]
#[method(attemptRotationToDeviceOrientation)]
pub fn attemptRotationToDeviceOrientation();
#[sel(setNeedsStatusBarAppearanceUpdate)]
#[method(setNeedsStatusBarAppearanceUpdate)]
pub fn setNeedsStatusBarAppearanceUpdate(&self);
#[sel(setNeedsUpdateOfHomeIndicatorAutoHidden)]
#[method(setNeedsUpdateOfHomeIndicatorAutoHidden)]
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
#[sel(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
#[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
pub fn view(&self) -> Option<Id<UIView, Shared>> {
pub fn view(&self) -> Option<Id<UIView>> {
unsafe { msg_send_id![self, view] }
}
#[sel(setView:)]
#[method(setView:)]
pub fn setView(&self, view: Option<&UIView>);
}
);

View File

@@ -1,6 +1,6 @@
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use icrate::Foundation::NSObject;
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIScreen, UIView};
@@ -11,25 +11,26 @@ extern_class!(
unsafe impl ClassType for UIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIWindow {
pub fn screen(&self) -> Id<UIScreen, Shared> {
pub fn screen(&self) -> Id<UIScreen> {
unsafe { msg_send_id![self, screen] }
}
#[sel(setScreen:)]
#[method(setScreen:)]
pub fn setScreen(&self, screen: &UIScreen);
#[sel(setHidden:)]
#[method(setHidden:)]
pub fn setHidden(&self, flag: bool);
#[sel(makeKeyAndVisible)]
#[method(makeKeyAndVisible)]
pub fn makeKeyAndVisible(&self);
#[sel(isKeyWindow)]
#[method(isKeyWindow)]
pub fn isKeyWindow(&self) -> bool;
}
);

View File

@@ -1,9 +1,12 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::ptr::NonNull;
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2::rc::{Id, Shared};
use objc2::runtime::Class;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, ClassType};
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::Id;
use objc2::runtime::AnyClass;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
@@ -26,32 +29,28 @@ use crate::{
};
declare_class!(
pub(crate) struct WinitView {}
pub(crate) struct WinitView;
unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIView";
}
unsafe impl WinitView {
#[sel(drawRect:)]
#[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) {
let window = self.window().unwrap();
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.id()),
)))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::RedrawEventsCleared,
))),
);
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.id()),
)));
}
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
#[sel(layoutSubviews)]
#[method(layoutSubviews)]
fn layout_subviews(&self) {
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
@@ -83,7 +82,7 @@ declare_class!(
}
}
#[sel(setContentScaleFactor:)]
#[method(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let _: () =
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
@@ -133,22 +132,22 @@ declare_class!(
}
}
#[sel(touchesBegan:withEvent:)]
#[method(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesMoved:withEvent:)]
#[method(touchesMoved:withEvent:)]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesEnded:withEvent:)]
#[method(touchesEnded:withEvent:)]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesCancelled:withEvent:)]
#[method(touchesCancelled:withEvent:)]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
@@ -158,17 +157,17 @@ declare_class!(
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitView {
fn window(&self) -> Option<Id<WinitUIWindow, Shared>> {
fn window(&self) -> Option<Id<WinitUIWindow>> {
unsafe { msg_send_id![self, window] }
}
unsafe fn traitCollection(&self) -> Id<UITraitCollection, Shared> {
unsafe fn traitCollection(&self) -> Id<UITraitCollection> {
msg_send_id![self, traitCollection]
}
// TODO: Allow the user to customize this
#[sel(layerClass)]
pub(crate) fn layerClass() -> &'static Class;
#[method(layerClass)]
pub(crate) fn layerClass() -> &'static AnyClass;
}
);
@@ -178,9 +177,8 @@ impl WinitView {
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
@@ -203,7 +201,9 @@ impl WinitView {
let trait_collection = unsafe { self.traitCollection() };
let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available {
if touch_capability == UIForceTouchCapability::Available
|| touch_type == UITouchType::Pencil
{
let force = touch.force();
let max_possible_force = touch.maximumPossibleForce();
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
@@ -260,102 +260,108 @@ impl WinitView {
}
}
pub struct ViewControllerState {
prefers_status_bar_hidden: Cell<bool>,
prefers_home_indicator_auto_hidden: Cell<bool>,
supported_orientations: Cell<UIInterfaceOrientationMask>,
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
}
declare_class!(
pub(crate) struct WinitViewController {
_prefers_status_bar_hidden: bool,
_prefers_home_indicator_auto_hidden: bool,
_supported_orientations: UIInterfaceOrientationMask,
_preferred_screen_edges_deferring_system_gestures: UIRectEdge,
state: IvarDrop<Box<ViewControllerState>, "_state">,
}
mod view_controller_ivars;
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
type Super = UIViewController;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIViewController";
}
unsafe impl WinitViewController {
#[sel(shouldAutorotate)]
#[method(init)]
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = msg_send![super(this), init];
this.map(|this| {
// These are set in WinitViewController::new, it's just to set them
// to _something_.
Ivar::write(
&mut this.state,
Box::new(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(
UIRectEdge::NONE,
),
}),
);
NonNull::from(this)
})
}
}
unsafe impl WinitViewController {
#[method(shouldAutorotate)]
fn should_autorotate(&self) -> bool {
true
}
}
unsafe impl WinitViewController {
#[sel(prefersStatusBarHidden)]
#[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool {
*self._prefers_status_bar_hidden
self.state.prefers_status_bar_hidden.get()
}
#[sel(setPrefersStatusBarHidden:)]
fn set_prefers_status_bar_hidden(&mut self, val: bool) {
*self._prefers_status_bar_hidden = val;
self.setNeedsStatusBarAppearanceUpdate();
}
#[sel(prefersHomeIndicatorAutoHidden)]
#[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
*self._prefers_home_indicator_auto_hidden
self.state.prefers_home_indicator_auto_hidden.get()
}
#[sel(setPrefersHomeIndicatorAutoHidden:)]
fn set_prefers_home_indicator_auto_hidden(&mut self, val: bool) {
*self._prefers_home_indicator_auto_hidden = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
#[sel(supportedInterfaceOrientations)]
#[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
*self._supported_orientations
self.state.supported_orientations.get()
}
#[sel(setSupportedInterfaceOrientations:)]
fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) {
*self._supported_orientations = val;
UIViewController::attemptRotationToDeviceOrientation();
}
#[sel(preferredScreenEdgesDeferringSystemGestures)]
#[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
*self._preferred_screen_edges_deferring_system_gestures
self.state
.preferred_screen_edges_deferring_system_gestures
.get()
}
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
fn set_preferred_screen_edges_deferring_system_gestures(&mut self, val: UIRectEdge) {
*self._preferred_screen_edges_deferring_system_gestures = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
}
);
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitViewController {
#[sel(setPrefersStatusBarHidden:)]
pub(crate) fn setPrefersStatusBarHidden(&self, flag: bool);
#[sel(setSupportedInterfaceOrientations:)]
pub(crate) fn setSupportedInterfaceOrientations(&self, val: UIInterfaceOrientationMask);
#[sel(setPrefersHomeIndicatorAutoHidden:)]
pub(crate) fn setPrefersHomeIndicatorAutoHidden(&self, val: bool);
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
pub(crate) fn setPreferredScreenEdgesDeferringSystemGestures(&self, val: UIRectEdge);
}
);
impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
self.state.prefers_status_bar_hidden.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.state.prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
self.state
.preferred_screen_edges_deferring_system_gestures
.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
pub(crate) fn set_supported_interface_orientations(
&self,
mtm: MainThreadMarker,
@@ -378,7 +384,8 @@ impl WinitViewController {
| UIInterfaceOrientationMask::PortraitUpsideDown
}
};
self.setSupportedInterfaceOrientations(mask);
self.state.supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation();
}
pub(crate) fn new(
@@ -386,17 +393,18 @@ impl WinitViewController {
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] };
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
this.setPrefersStatusBarHidden(platform_attributes.prefers_status_bar_hidden);
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
this.setPrefersHomeIndicatorAutoHidden(platform_attributes.prefers_home_indicator_hidden);
this.set_prefers_home_indicator_auto_hidden(
platform_attributes.prefers_home_indicator_hidden,
);
this.setPreferredScreenEdgesDeferringSystemGestures(
this.set_preferred_screen_edges_deferring_system_gestures(
platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into(),
@@ -410,15 +418,17 @@ impl WinitViewController {
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow {}
pub(crate) struct WinitUIWindow;
unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIWindow;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIWindow";
}
unsafe impl WinitUIWindow {
#[sel(becomeKeyWindow)]
#[method(becomeKeyWindow)]
fn become_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
@@ -429,7 +439,7 @@ declare_class!(
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
#[sel(resignKeyWindow)]
#[method(resignKeyWindow)]
fn resign_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
@@ -449,9 +459,8 @@ impl WinitUIWindow {
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
view_controller: &UIViewController,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
@@ -478,15 +487,17 @@ impl WinitUIWindow {
}
declare_class!(
pub struct WinitApplicationDelegate {}
pub struct WinitApplicationDelegate;
unsafe impl ClassType for WinitApplicationDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
// UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate {
#[sel(application:didFinishLaunchingWithOptions:)]
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
unsafe {
app_state::did_finish_launching();
@@ -494,22 +505,23 @@ declare_class!(
true
}
#[sel(applicationDidBecomeActive:)]
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
}
#[sel(applicationWillResignActive:)]
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
}
#[sel(applicationWillEnterForeground:)]
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, _application: &UIApplication) {}
#[sel(applicationDidEnterBackground:)]
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, _application: &UIApplication) {}
#[sel(applicationWillTerminate:)]
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
let mut events = Vec::new();
for window in application.windows().iter() {

View File

@@ -1,14 +1,10 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::VecDeque,
ffi::c_void,
ops::{Deref, DerefMut},
};
use std::collections::VecDeque;
use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{class, msg_send};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
@@ -23,7 +19,6 @@ use crate::{
platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
ffi::UIRectEdge,
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
},
window::{
@@ -33,9 +28,9 @@ use crate::{
};
pub struct Inner {
pub(crate) window: Id<WinitUIWindow, Shared>,
pub(crate) view_controller: Id<WinitViewController, Shared>,
pub(crate) view: Id<WinitView, Shared>,
window: Id<WinitUIWindow>,
view_controller: Id<WinitViewController>,
view: Id<WinitView>,
gl_or_metal_backed: bool,
}
@@ -75,73 +70,65 @@ impl Inner {
}
}
pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
unsafe {
let safe_area = self.safe_area_screen_space();
let position = LogicalPosition {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
let safe_area = self.safe_area_screen_space();
let position = LogicalPosition {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
unsafe {
let screen_frame = self.screen_frame();
let position = LogicalPosition {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
let screen_frame = self.screen_frame();
let position = LogicalPosition {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn set_outer_position(&self, physical_position: Position) {
unsafe {
let scale_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
x: position.x as _,
y: position.y as _,
},
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
self.window.setBounds(bounds);
}
let scale_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
x: position.x as _,
y: position.y as _,
},
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
self.window.setBounds(bounds);
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
unsafe {
let scale_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(scale_factor)
}
let scale_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(scale_factor)
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
unsafe {
let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(scale_factor)
}
let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(scale_factor)
}
pub fn set_inner_size(&self, _size: Size) {
warn!("not clear what `Window::set_inner_size` means on iOS");
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.inner_size())
}
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
@@ -262,22 +249,20 @@ impl Inner {
}
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
unsafe {
let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds();
let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds();
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(Fullscreen::Borderless(Some(monitor)))
} else {
None
}
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(Fullscreen::Borderless(Some(monitor)))
} else {
None
}
}
@@ -375,32 +360,7 @@ impl Inner {
}
pub struct Window {
pub inner: Inner,
}
impl Drop for Window {
fn drop(&mut self) {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Deref for Window {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for Window {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&mut self.inner
}
inner: MainThreadBound<Inner>,
}
impl Window {
@@ -497,29 +457,29 @@ impl Window {
}
}
let inner = Inner {
window,
view_controller,
view,
gl_or_metal_backed,
};
Ok(Window {
inner: Inner {
window,
view_controller,
view,
gl_or_metal_backed,
},
inner: MainThreadBound::new(inner, mtm),
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
// For now, don't actually do queuing, since it may be less predictable
self.maybe_wait_on_main(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
self.inner.get_on_main(|inner, _mtm| f(inner))
}
}
// WindowExtIOS
impl Inner {
pub fn ui_window(&self) -> *mut c_void {
Id::as_ptr(&self.window) as *mut c_void
}
pub fn ui_view_controller(&self) -> *mut c_void {
Id::as_ptr(&self.view_controller) as *mut c_void
}
pub fn ui_view(&self) -> *mut c_void {
Id::as_ptr(&self.view) as *mut c_void
}
pub fn set_scale_factor(&self, scale_factor: f64) {
assert!(
dpi::validate_scale_factor(scale_factor),
@@ -538,42 +498,37 @@ impl Inner {
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.view_controller
.setPrefersHomeIndicatorAutoHidden(hidden);
.set_prefers_home_indicator_auto_hidden(hidden);
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
self.view_controller
.setPreferredScreenEdgesDeferringSystemGestures(edges);
.set_preferred_screen_edges_deferring_system_gestures(edges.into());
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.view_controller.setPrefersStatusBarHidden(hidden);
self.view_controller.set_prefers_status_bar_hidden(hidden);
}
}
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(self.window.bounds())
}
// requires main thread
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_toCoordinateSpace(rect, &screen_space)
}
// requires main thread
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_fromCoordinateSpace(rect, &screen_space)
}
// requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
fn safe_area_screen_space(&self) -> CGRect {
let bounds = self.window.bounds();
if app_state::os_capabilities().safe_area {
let safe_area = self.window.safeAreaInsets();
@@ -591,7 +546,7 @@ impl Inner {
} else {
let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
);
app.statusBarFrame()
@@ -648,8 +603,8 @@ impl From<u64> for WindowId {
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl From<&Object> for WindowId {
fn from(window: &Object) -> WindowId {
impl From<&AnyObject> for WindowId {
fn from(window: &AnyObject) -> WindowId {
WindowId {
window: window as *const _ as _,
}

View File

@@ -354,7 +354,6 @@ impl KbdState {
self.post_init(state, keymap);
}
#[cfg(feature = "wayland")]
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 }
}

View File

@@ -3,45 +3,45 @@
#[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(wayland_platform)]
use std::error::Error;
use std::sync::Arc;
use std::time::Duration;
use std::{collections::VecDeque, env, fmt};
#[cfg(x11_platform)]
use std::{
ffi::CStr,
mem::MaybeUninit,
os::raw::*,
sync::{Arc, Mutex},
};
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
#[cfg(x11_platform)]
use once_cell::sync::Lazy;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use smol_str::SmolStr;
#[cfg(x11_platform)]
pub use self::x11::XNotSupported;
#[cfg(x11_platform)]
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, KeyEvent},
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
event_loop::{
AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
EventLoopWindowTarget as RootELW,
},
icon::Icon,
keyboard::{Key, KeyCode},
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
platform::{
modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus,
scancode::KeyCodeExtScancode,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme,
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
},
};
#[cfg(x11_platform)]
pub use x11::XNotSupported;
#[cfg(x11_platform)]
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
pub(crate) use crate::platform_impl::Fullscreen;
pub mod common;
#[cfg(wayland_platform)]
@@ -49,15 +49,6 @@ pub mod wayland;
#[cfg(x11_platform)]
pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
///
/// Legal values are x11 and wayland. If this variable is set only the named backend
/// will be tried by winit. 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.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend {
#[cfg(x11_platform)]
@@ -87,32 +78,38 @@ impl ApplicationName {
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub name: Option<ApplicationName>,
pub activation_token: Option<ActivationToken>,
#[cfg(x11_platform)]
pub visual_infos: Option<XVisualInfo>,
#[cfg(x11_platform)]
pub x11: X11WindowBuilderAttributes,
}
#[derive(Clone)]
#[cfg(x11_platform)]
pub struct X11WindowBuilderAttributes {
pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
pub screen_id: Option<i32>,
#[cfg(x11_platform)]
pub base_size: Option<Size>,
#[cfg(x11_platform)]
pub override_redirect: bool,
#[cfg(x11_platform)]
pub x11_window_types: Vec<XWindowType>,
/// The parent window to embed this window into.
pub embed_window: Option<x11rb::protocol::xproto::Window>,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
name: None,
activation_token: None,
#[cfg(x11_platform)]
visual_infos: None,
#[cfg(x11_platform)]
screen_id: None,
#[cfg(x11_platform)]
base_size: None,
#[cfg(x11_platform)]
override_redirect: false,
#[cfg(x11_platform)]
x11_window_types: vec![XWindowType::Normal],
x11: X11WindowBuilderAttributes {
visual_id: None,
screen_id: None,
base_size: None,
override_redirect: false,
x11_window_types: vec![XWindowType::Normal],
embed_window: None,
},
}
}
}
@@ -123,23 +120,21 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
#[derive(Debug, Clone)]
pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)]
XError(XError),
#[cfg(x11_platform)]
XMisc(&'static str),
XError(Arc<X11Error>),
#[cfg(wayland_platform)]
WaylandMisc(&'static str),
WaylandError(Arc<wayland::WaylandError>),
}
impl fmt::Display for OsError {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
OsError::Misc(e) => _f.pad(e),
#[cfg(x11_platform)]
OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(x11_platform)]
OsError::XMisc(e) => _f.pad(e),
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
#[cfg(wayland_platform)]
OsError::WaylandMisc(e) => _f.pad(e),
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
}
}
}
@@ -311,6 +306,14 @@ impl Window {
}
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
#[inline]
pub fn id(&self) -> WindowId {
match self {
@@ -367,8 +370,13 @@ impl Window {
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
x11_or_wayland!(match self; Window(w) => w.set_inner_size(size))
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.request_inner_size(size))
}
#[inline]
pub(crate) fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.request_activation_token())
}
#[inline]
@@ -541,12 +549,7 @@ impl Window {
}
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(wayland_platform)]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
}
#[inline]
@@ -554,6 +557,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.request_redraw())
}
#[inline]
pub fn pre_present_notify(&self) {
x11_or_wayland!(match self; Window(w) => w.pre_present_notify())
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
match self {
@@ -730,7 +738,9 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
if !attributes.any_thread && !is_main_thread() {
panic!(
"Initializing the event loop outside of the main thread is a significant \
@@ -740,65 +750,26 @@ impl<T: 'static> EventLoop<T> {
);
}
#[cfg(x11_platform)]
if attributes.forced_backend == Some(Backend::X) {
// TODO: Propagate
return EventLoop::new_x11_any_thread().unwrap();
}
// NOTE: Wayland first because of X11 could be present under wayland as well.
#[cfg(wayland_platform)]
if attributes.forced_backend == Some(Backend::Wayland) {
// TODO: Propagate
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
if attributes.forced_backend == Some(Backend::Wayland)
|| env::var("WAYLAND_DISPLAY").is_ok()
{
return EventLoop::new_wayland_any_thread().map_err(Into::into);
}
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
match env_var.as_str() {
"x11" => {
// TODO: propagate
#[cfg(x11_platform)]
return EventLoop::new_x11_any_thread()
.expect("Failed to initialize X11 backend");
#[cfg(not(x11_platform))]
panic!("x11 feature is not enabled")
}
"wayland" => {
#[cfg(wayland_platform)]
return EventLoop::new_wayland_any_thread()
.expect("Failed to initialize Wayland backend");
#[cfg(not(wayland_platform))]
panic!("wayland feature is not enabled");
}
_ => panic!(
"Unknown environment variable value for {BACKEND_PREFERENCE_ENV_VAR}, try one of `x11`,`wayland`",
),
}
}
#[cfg(wayland_platform)]
let wayland_err = match EventLoop::new_wayland_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(x11_platform)]
let x11_err = match EventLoop::new_x11_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
if attributes.forced_backend == Some(Backend::X) || env::var("DISPLAY").is_ok() {
return Ok(EventLoop::new_x11_any_thread().unwrap());
}
#[cfg(not(wayland_platform))]
let wayland_err = "backend disabled";
#[cfg(not(x11_platform))]
let x11_err = "backend disabled";
panic!(
"Failed to initialize any backend! Wayland status: {wayland_err:?} X11 status: {x11_err:?}",
);
Err(EventLoopError::Os(os_error!(OsError::Misc(
"neither WAYLAND_DISPLAY nor DISPLAY is set."
))))
}
#[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
@@ -816,18 +787,25 @@ 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) -> i32
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback))
self.run_ondemand(callback)
}
pub fn run<F>(self, callback: F) -> !
pub fn run_ondemand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback))
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_ondemand(callback))
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW<T>, &mut ControlFlow),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
@@ -907,12 +885,12 @@ impl<T> EventLoopWindowTarget<T> {
}
fn sticky_exit_callback<T, F>(
evt: Event<'_, T>,
evt: Event<T>,
target: &RootELW<T>,
control_flow: &mut ControlFlow,
callback: &mut F,
) where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
@@ -923,11 +901,18 @@ fn sticky_exit_callback<T, F>(
}
}
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| {
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
})
}
#[cfg(target_os = "linux")]
fn is_main_thread() -> bool {
use libc::{c_long, getpid, syscall, SYS_gettid};
unsafe { syscall(SYS_gettid) == getpid() as c_long }
rustix::thread::gettid() == rustix::process::getpid()
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]

View File

@@ -1,26 +1,29 @@
//! The event-loop routines.
use std::cell::RefCell;
use std::error::Error;
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::process;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
use sctk::reexports::calloop;
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::event::{Event, StartCause, WindowEvent};
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError};
mod proxy;
pub mod sink;
@@ -29,12 +32,23 @@ pub use proxy::EventLoopProxy;
use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState};
use super::{DeviceId, WindowId};
use super::window::state::FrameCallbackState;
use super::{DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop.
pub struct EventLoop<T: 'static> {
/// Has `run` or `run_ondemand` been called or a call to `pump_events` that starts the loop
loop_running: bool,
/// The application's latest control_flow state
control_flow: ControlFlow,
buffer_sink: EventSink,
compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
@@ -59,50 +73,93 @@ pub struct EventLoop<T: 'static> {
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
let connection = Connection::connect_to_env()?;
pub fn new() -> Result<EventLoop<T>, EventLoopError> {
macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
};
}
let (globals, mut event_queue) = globals::registry_queue_init(&connection)?;
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
let (globals, mut event_queue) = map_err!(
globals::registry_queue_init(&connection),
WaylandError::Global
)?;
let queue_handle = event_queue.handle();
let event_loop = calloop::EventLoop::<WinitState>::try_new()?;
let event_loop = map_err!(
calloop::EventLoop::<WinitState>::try_new(),
WaylandError::Calloop
)?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
.map_err(|error| os_error!(error))?;
// NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server.
event_queue.roundtrip(&mut winit_state)?;
map_err!(
event_queue.roundtrip(&mut winit_state),
WaylandError::Dispatch
)?;
// Register Wayland source.
let wayland_source = WaylandSource::new(event_queue)?;
let wayland_source = map_err!(WaylandSource::new(event_queue), WaylandError::Wire)?;
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state)
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| {
let result = queue.dispatch_pending(winit_state);
if result.is_ok()
&& (!winit_state.events_sink.is_empty()
|| !winit_state.window_compositor_updates.is_empty())
{
winit_state.dispatched_events = true;
}
result
});
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
map_err!(
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone()),
WaylandError::Calloop
)?;
// Setup the user proxy.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
event_loop
let result = event_loop
.handle()
.insert_source(user_events_channel, move |event, _, _| {
if let calloop::channel::Event::Msg(msg) = event {
pending_user_events_clone.borrow_mut().push(msg);
}
})?;
.insert_source(
user_events_channel,
move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
},
)
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
// An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
event_loop
let (event_loop_awakener, event_loop_awakener_source) = map_err!(
calloop::ping::make_ping()
.map_err(|error| CalloopError::OtherError(Box::new(error).into())),
WaylandError::Calloop
)?;
let result = event_loop
.handle()
.insert_source(event_loop_awakener_source, move |_, _, _| {
// No extra handling is required, we just need to wake-up.
})?;
.insert_source(
event_loop_awakener_source,
move |_, _, winit_state: &mut WinitState| {
// Mark that we have something to dispatch.
winit_state.dispatched_events = true;
},
)
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
let window_target = EventLoopWindowTarget {
connection: connection.clone(),
@@ -114,6 +171,11 @@ impl<T: 'static> EventLoop<T> {
};
let event_loop = Self {
loop_running: false,
control_flow: ControlFlow::default(),
compositor_updates: Vec::new(),
buffer_sink: EventSink::default(),
window_ids: Vec::new(),
connection,
wayland_dispatcher,
user_events_sender,
@@ -128,45 +190,87 @@ impl<T: 'static> EventLoop<T> {
Ok(event_loop)
}
pub fn run<F>(mut self, callback: F) -> !
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
let exit_code = self.run_return(callback);
process::exit(exit_code);
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
}
let exit = loop {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
}
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
}
_ => {
continue;
}
}
};
// Applications aren't allowed to carry windows between separate
// `run_ondemand` calls but if they have only just dropped their
// windows we need to make sure those last requests are sent to the
// compositor.
let _ = self.roundtrip().map_err(EventLoopError::Os);
exit
}
pub fn run_return<F>(&mut self, mut callback: F) -> i32
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::Poll;
if !self.loop_running {
self.loop_running = true;
// XXX preallocate certian structures to avoid allocating on each loop iteration.
let mut window_ids = Vec::<WindowId>::new();
let mut compositor_updates = Vec::<WindowCompositorUpdate>::new();
let mut buffer_sink = EventSink::new();
// Reset the internal state for the loop as we start running to
// ensure consistent behaviour in case the loop runs and exits more
// than once.
self.control_flow = ControlFlow::Poll;
callback(
Event::NewEvents(StartCause::Init),
&self.window_target,
&mut control_flow,
);
// Run the initial loop iteration.
self.single_iteration(&mut callback, StartCause::Init);
}
// XXX For consistency all platforms must emit a 'Resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
callback(Event::Resumed, &self.window_target, &mut control_flow);
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !matches!(self.control_flow, ControlFlow::ExitWithCode(_)) {
self.poll_events_with_timeout(timeout, &mut callback);
}
if let ControlFlow::ExitWithCode(code) = self.control_flow {
self.loop_running = false;
// XXX We break on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = loop {
// Flush the connection.
let _ = self.connection.flush();
let mut dummy = self.control_flow;
sticky_exit_callback(
Event::LoopExiting,
self.window_target(),
&mut dummy,
&mut callback,
);
PumpStatus::Exit(code)
} else {
PumpStatus::Continue
}
}
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
let cause = loop {
let start = Instant::now();
// TODO(rib): remove this workaround and instead make sure that the calloop
// WaylandSource correctly implements the cooperative prepare_read protocol
// that support multithreaded wayland clients that may all read from the
// same socket.
//
// During the run of the user callback, some other code monitoring and reading the
// Wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
@@ -186,272 +290,321 @@ impl<T: 'static> EventLoop<T> {
};
match queue.dispatch_pending(state) {
Ok(dispatched) => dispatched > 0,
Ok(dispatched) => {
state.dispatched_events |= !state.events_sink.is_empty()
|| !state.window_compositor_updates.is_empty();
dispatched > 0
}
Err(error) => {
error!("Error dispatching wayland queue: {}", error);
break 1;
self.control_flow = ControlFlow::ExitWithCode(1);
return;
}
}
};
match control_flow {
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::ZERO;
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
timeout = if instant_wakeup {
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
}
// This function shouldn't have to handle any requests to exit
// the application (there should be no need to poll for events
// if the application has requested to exit) so we consider
// it a bug in the backend if we ever see `ExitWithCode` here.
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
min_timeout(control_flow_timeout, timeout)
};
callback(
Event::NewEvents(StartCause::Poll),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::ZERO)
} else {
None
};
// NOTE Ideally we should flush as the last thing we do before polling
// to wait for events, and this should be done by the calloop
// WaylandSource but we currently need to flush writes manually.
let _ = self.connection.flush();
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::ZERO
};
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,
)
}
}
if let Err(error) = self.loop_dispatch(timeout) {
// NOTE We exit on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = error.raw_os_error().unwrap_or(1);
self.control_flow = ControlFlow::ExitWithCode(exit_code);
return;
}
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in self.pending_user_events.borrow_mut().drain(..) {
// NB: `StartCause::Init` is handled as a special case and doesn't need
// to be considered here
let cause = match self.control_flow {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
requested_resume: None,
},
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}
} else {
StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}
}
}
// This function shouldn't have to handle any requests to exit
// the application (there should be no need to poll for events
// if the application has requested to exit) so we consider
// it a bug in the backend if we ever see `ExitWithCode` here.
ControlFlow::ExitWithCode(_code) => unreachable!(),
};
// Reduce spurious wake-ups.
let dispatched_events = self.with_state(|state| state.dispatched_events);
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
continue;
}
break cause;
};
self.single_iteration(&mut callback, cause);
}
fn single_iteration<F>(&mut self, mut callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
// NOTE currently just indented to simplify the diff
let mut control_flow = self.control_flow;
// We retain these grow-only scratch buffers as part of the EventLoop
// for the sake of avoiding lots of reallocs. We take them here to avoid
// trying to mutably borrow `self` more than once and we swap them back
// when finished.
let mut compositor_updates = std::mem::take(&mut self.compositor_updates);
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
sticky_exit_callback(
Event::NewEvents(cause),
&self.window_target,
&mut control_flow,
callback,
);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
sticky_exit_callback(
Event::Resumed,
&self.window_target,
&mut control_flow,
callback,
);
}
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in self.pending_user_events.borrow_mut().drain(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// Drain the pending compositor updates.
self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates));
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if let Some(scale_factor) = compositor_update.scale_factor {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
sticky_exit_callback(
Event::UserEvent(user_event),
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
},
&self.window_target,
&mut control_flow,
&mut callback,
);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let new_logical_size = physical_size.to_logical(scale_factor);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.resize(new_logical_size);
});
}
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
}
if let Some(size) = compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let physical_size = logical_to_physical_rounded(size, scale_factor);
// TODO could probably bring back size reporting optimization.
// Mark the window as needed a redraw.
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
physical_size
});
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// Drain the pending compositor updates.
self.with_state(|state| {
compositor_updates.append(&mut state.window_compositor_updates)
});
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if let Some(scale_factor) = compositor_update.scale_factor {
let mut physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
},
},
&self.window_target,
&mut control_flow,
&mut callback,
);
let new_logical_size = physical_size.to_logical(scale_factor);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.resize(new_logical_size);
});
}
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
}
if let Some(size) = compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let physical_size = logical_to_physical_rounded(size, scale_factor);
// TODO could probably bring back size reporting optimization.
// Mark the window as needed a redraw.
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
physical_size
});
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
if compositor_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
if compositor_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// Push the events directly from the window.
self.with_state(|state| {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Push the events directly from the window.
self.with_state(|state| {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Handle non-synthetic events.
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Handle non-synthetic events.
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Send events cleared.
sticky_exit_callback(
Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
// Collect the window ids
self.with_state(|state| {
window_ids.extend(state.window_requests.get_mut().keys());
});
// Collect the window ids
self.with_state(|state| {
window_ids.extend(state.window_requests.get_mut().keys());
});
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
false
} else {
let mut window = state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap();
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
if window.frame_callback_state() == FrameCallbackState::Requested {
false
} else {
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Redraw the frames while at it.
redraw_requested |= state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap()
.refresh_frame();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested
}
});
if request_redraw {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
});
if request_redraw {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
}
// Send RedrawEventCleared.
sticky_exit_callback(
Event::RedrawEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
};
// Reset the hint that we've dispatched events.
self.with_state(|state| {
state.dispatched_events = false;
});
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
exit_code
// This is always the last event we dispatch before poll again
sticky_exit_callback(
Event::AboutToWait,
&self.window_target,
&mut control_flow,
&mut callback,
);
self.control_flow = control_flow;
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
std::mem::swap(&mut self.window_ids, &mut window_ids);
}
#[inline]
@@ -486,6 +639,22 @@ impl<T: 'static> EventLoop<T> {
error.into()
})
}
fn roundtrip(&mut self) -> Result<usize, RootOsError> {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let event_queue = wayland_source.queue();
event_queue.roundtrip(state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
error
))))
})
}
}
pub struct EventLoopWindowTarget<T> {

View File

@@ -12,7 +12,7 @@ use super::{DeviceId, WindowId};
/// to the winit's user.
#[derive(Default)]
pub struct EventSink {
pub window_events: Vec<Event<'static, ()>>,
pub window_events: Vec<Event<()>>,
}
impl EventSink {
@@ -20,6 +20,12 @@ impl EventSink {
Default::default()
}
/// Return `true` if there're pending events.
#[inline]
pub fn is_empty(&self) -> bool {
self.window_events.is_empty()
}
/// Add new device event to a queue.
#[inline]
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
@@ -31,7 +37,7 @@ impl EventSink {
/// Add new window event to a queue.
#[inline]
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(window_id),
@@ -44,7 +50,7 @@ impl EventSink {
}
#[inline]
pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> {
pub fn drain(&mut self) -> Drain<'_, Event<()>> {
self.window_events.drain(..)
}
}

View File

@@ -2,10 +2,14 @@
//! Winit's Wayland backend.
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
use std::fmt::Display;
use std::sync::Arc;
pub use crate::platform_impl::platform::WindowId;
use sctk::reexports::client::globals::{BindError, GlobalError};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
pub use crate::platform_impl::platform::{OsError, WindowId};
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
pub use window::Window;
@@ -17,6 +21,46 @@ mod state;
mod types;
mod window;
#[derive(Debug)]
pub enum WaylandError {
/// Error connecting to the socket.
Connection(ConnectError),
/// Error binding the global.
Global(GlobalError),
// Bind error.
Bind(BindError),
/// Error during the dispatching the event queue.
Dispatch(DispatchError),
/// Calloop error.
Calloop(calloop::Error),
/// Wayland
Wire(client::backend::WaylandError),
}
impl Display for WaylandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WaylandError::Connection(error) => error.fmt(f),
WaylandError::Global(error) => error.fmt(f),
WaylandError::Bind(error) => error.fmt(f),
WaylandError::Dispatch(error) => error.fmt(f),
WaylandError::Calloop(error) => error.fmt(f),
WaylandError::Wire(error) => error.fmt(f),
}
}
}
impl From<WaylandError> for OsError {
fn from(value: WaylandError) -> Self {
Self::WaylandError(Arc::new(value))
}
}
/// Dummy device id, since Wayland doesn't have device events.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;

View File

@@ -151,6 +151,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
keyboard_state.repeat_token = keyboard_state
.loop_handle
.insert_source(timer, move |_, _, state| {
// Required to handle the wakeups from the repeat sources.
state.dispatched_events = true;
let data = wl_keyboard.data::<KeyboardData>().unwrap();
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();

View File

@@ -68,7 +68,7 @@ impl PointerHandler for WinitState {
if parent_surface != surface =>
{
if let Some(icon) =
window.frame_point_moved(surface, event.position.0, event.position.1)
window.frame_point_moved(seat, surface, event.position.0, event.position.1)
{
if let Some(pointer) = seat_state.pointer.as_ref() {
let surface = pointer

View File

@@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::error::Error;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use fnv::FnvHashMap;
@@ -23,6 +23,7 @@ use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;
use crate::dpi::LogicalSize;
use crate::platform_impl::OsError;
use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
@@ -34,7 +35,7 @@ use super::types::wp_fractional_scaling::FractionalScalingManager;
use super::types::wp_viewporter::ViewporterState;
use super::types::xdg_activation::XdgActivationState;
use super::window::{WindowRequests, WindowState};
use super::WindowId;
use super::{WaylandError, WindowId};
/// Winit's Wayland state.
pub struct WinitState {
@@ -104,6 +105,10 @@ pub struct WinitState {
/// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>,
/// Whether we have dispatched events to the user thus we want to
/// send `AboutToWait` and normally wakeup the user.
pub dispatched_events: bool,
}
impl WinitState {
@@ -111,14 +116,16 @@ impl WinitState {
globals: &GlobalList,
queue_handle: &QueueHandle<Self>,
loop_handle: LoopHandle<'static, WinitState>,
) -> Result<Self, Box<dyn Error>> {
) -> Result<Self, OsError> {
let registry_state = RegistryState::new(globals);
let compositor_state = CompositorState::bind(globals, queue_handle)?;
let compositor_state =
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let subcompositor_state = SubcompositorState::bind(
compositor_state.wl_compositor().clone(),
globals,
queue_handle,
)?;
)
.map_err(WaylandError::Bind)?;
let output_state = OutputState::new(globals, queue_handle);
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
@@ -143,9 +150,9 @@ impl WinitState {
subcompositor_state: Arc::new(subcompositor_state),
output_state,
seat_state,
shm: Shm::bind(globals, queue_handle)?,
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_shell: XdgShell::bind(globals, queue_handle)?,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
windows: Default::default(),
@@ -167,6 +174,8 @@ impl WinitState {
monitors: Arc::new(Mutex::new(monitors)),
events_sink: EventSink::new(),
loop_handle,
// Make it true by default.
dispatched_events: true,
})
}
@@ -321,7 +330,27 @@ impl CompositorHandler for WinitState {
self.scale_factor_changed(surface, scale_factor as f64, true)
}
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: u32) {}
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, surface: &WlSurface, _: u32) {
let window_id = super::make_wid(surface);
let window = match self.windows.get_mut().get(&window_id) {
Some(window) => window,
None => return,
};
// In case we have a redraw requested we must indicate the wake up.
if self
.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.load(Ordering::Relaxed)
{
self.dispatched_events = true;
}
window.lock().unwrap().frame_callback_received();
}
}
impl ProvidesRegistryState for WinitState {

View File

@@ -16,7 +16,10 @@ use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::
use sctk::globals::GlobalData;
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::WindowId;
use crate::window::ActivationToken;
pub struct XdgActivationState {
xdg_activation: XdgActivationV1,
@@ -62,16 +65,29 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
_ => return,
};
state
let global = state
.xdg_activation
.as_ref()
.expect("got xdg_activation event without global.")
.global()
.activate(token, &data.surface);
.global();
// Mark that no request attention is in process.
if let Some(attention_requested) = data.attention_requested.upgrade() {
attention_requested.store(false, std::sync::atomic::Ordering::Relaxed);
match data {
XdgActivationTokenData::Attention((surface, fence)) => {
global.activate(token, surface);
// Mark that no request attention is in process.
if let Some(attention_requested) = fence.upgrade() {
attention_requested.store(false, std::sync::atomic::Ordering::Relaxed);
}
}
XdgActivationTokenData::Obtain((window_id, serial)) => {
state.events_sink.push_window_event(
crate::event::WindowEvent::ActivationTokenDone {
serial: *serial,
token: ActivationToken::_new(token),
},
*window_id,
);
}
}
proxy.destroy();
@@ -79,24 +95,11 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
}
/// The data associated with the activation request.
pub struct XdgActivationTokenData {
/// The surface we're raising.
surface: WlSurface,
/// Flag to throttle attention requests.
attention_requested: Weak<AtomicBool>,
}
impl XdgActivationTokenData {
/// Create a new data.
///
/// The `attenteion_requested` is marked as `false` on complition.
pub fn new(surface: WlSurface, attention_requested: Weak<AtomicBool>) -> Self {
Self {
surface,
attention_requested,
}
}
pub enum XdgActivationTokenData {
/// Request user attention for the given surface.
Attention((WlSurface, Weak<AtomicBool>)),
/// Get a token to be passed outside of the winit.
Obtain((WindowId, AsyncRequestSerial)),
}
delegate_dispatch!(WinitState: [ XdgActivationV1: GlobalData] => XdgActivationState);

View File

@@ -22,6 +22,7 @@ use sctk::shell::WaylandSurface;
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
@@ -35,9 +36,9 @@ use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
use super::state::WinitState;
use super::types::xdg_activation::XdgActivationTokenData;
use super::{EventLoopWindowTarget, WindowId};
use super::{EventLoopWindowTarget, WaylandError, WindowId};
mod state;
pub(crate) mod state;
pub use state::WindowState;
@@ -168,6 +169,14 @@ impl Window {
_ => (),
};
// Activate the window when the token is passed.
if let (Some(xdg_activation), Some(token)) = (
xdg_activation.as_ref(),
platform_attributes.activation_token,
) {
xdg_activation.activate(token._token, &surface);
}
// XXX Do initial commit.
window.commit();
@@ -196,18 +205,18 @@ impl Window {
let event_queue = wayland_source.queue();
// Do a roundtrip.
event_queue.roundtrip(&mut state).map_err(|_| {
os_error!(OsError::WaylandMisc(
"failed to do initial roundtrip for the window."
))
event_queue.roundtrip(&mut state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
error
))))
})?;
// XXX Wait for the initial configure to arrive.
while !window_state.lock().unwrap().is_configured() {
event_queue.blocking_dispatch(&mut state).map_err(|_| {
os_error!(OsError::WaylandMisc(
"failed to dispatch queue while waiting for initial configure."
))
event_queue.blocking_dispatch(&mut state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
error
))))
})?;
}
@@ -278,10 +287,23 @@ impl Window {
#[inline]
pub fn request_redraw(&self) {
self.window_requests
// NOTE: try to not wake up the loop when the event was already scheduled and not yet
// processed by the loop, because if at this point the value was `true` it could only
// mean that the loop still haven't dispatched the value to the client and will do
// eventually, resetting it to `false`.
if self
.window_requests
.redraw_requested
.store(true, Ordering::Relaxed);
self.event_loop_awakener.ping();
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
self.event_loop_awakener.ping();
}
}
#[inline]
pub fn pre_present_notify(&self) {
self.window_state.lock().unwrap().request_frame_callback();
}
#[inline]
@@ -292,13 +314,14 @@ impl Window {
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
// TODO should we issue the resize event? I don't think other platforms do so.
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let mut window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
window_state.resize(size.to_logical::<u32>(scale_factor));
self.request_redraw();
Some(window_state.inner_size().to_physical(scale_factor))
}
/// Set the minimum inner size for the window.
@@ -495,13 +518,31 @@ impl Window {
self.attention_requested.store(true, Ordering::Relaxed);
let surface = self.surface().clone();
let data =
XdgActivationTokenData::new(surface.clone(), Arc::downgrade(&self.attention_requested));
let data = XdgActivationTokenData::Attention((
surface.clone(),
Arc::downgrade(&self.attention_requested),
));
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
xdg_activation_token.set_surface(&surface);
xdg_activation_token.commit();
}
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
let xdg_activation = match self.xdg_activation.as_ref() {
Some(xdg_activation) => xdg_activation,
None => return Err(NotSupportedError::new()),
};
let serial = AsyncRequestSerial::get();
let data = XdgActivationTokenData::Obtain((self.window_id, serial));
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
xdg_activation_token.set_surface(self.surface());
xdg_activation_token.commit();
Ok(serial)
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
self.window_state.lock().unwrap().set_cursor_grab(mode)
@@ -533,9 +574,7 @@ impl Window {
Ok(())
} else {
let region = Region::new(&*self.compositor).map_err(|_| {
ExternalError::Os(os_error!(OsError::WaylandMisc(
"failed to set input region."
)))
ExternalError::Os(os_error!(OsError::Misc("failed to set input region.")))
})?;
region.add(0, 0, 0, 0);
surface.set_input_region(Some(region.wl_region()));
@@ -573,11 +612,6 @@ impl Window {
self.window_state.lock().unwrap().set_ime_purpose(purpose);
}
#[inline]
pub fn display(&self) -> &WlDisplay {
&self.display
}
#[inline]
pub fn surface(&self) -> &WlSurface {
self.window.wl_surface()

View File

@@ -125,30 +125,74 @@ pub struct WindowState {
/// sends `None` for the new size in the configure.
stateless_size: LogicalSize<u32>,
/// The state of the frame callback.
frame_callback_state: FrameCallbackState,
viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>,
}
/// The state of the cursor grabs.
#[derive(Clone, Copy)]
struct GrabState {
/// The grab mode requested by the user.
user_grab_mode: CursorGrabMode,
/// The current grab mode.
current_grab_mode: CursorGrabMode,
}
impl GrabState {
fn new() -> Self {
Self {
user_grab_mode: CursorGrabMode::None,
current_grab_mode: CursorGrabMode::None,
}
}
/// Whether the client side decorations have pending move operations.
///
/// The value is the serial of the event triggered moved.
has_pending_move: Option<u32>,
}
impl WindowState {
/// Create new window state.
pub fn new(
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
size: LogicalSize<u32>,
window: Window,
theme: Option<Theme>,
) -> Self {
let compositor = winit_state.compositor_state.clone();
let pointer_constraints = winit_state.pointer_constraints.clone();
let viewport = winit_state
.viewporter_state
.as_ref()
.map(|state| state.get_viewport(window.wl_surface(), queue_handle));
let fractional_scale = winit_state
.fractional_scaling_manager
.as_ref()
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
Self {
compositor,
connection,
csd_fails: false,
cursor_grab_mode: GrabState::new(),
cursor_icon: CursorIcon::Default,
cursor_visible: true,
decorate: true,
fractional_scale,
frame: None,
frame_callback_state: FrameCallbackState::None,
has_focus: false,
has_pending_move: None,
ime_allowed: false,
ime_purpose: ImePurpose::Normal,
last_configure: None,
max_inner_size: None,
min_inner_size: MIN_WINDOW_SIZE,
pointer_constraints,
pointers: Default::default(),
queue_handle: queue_handle.clone(),
resizable: true,
scale_factor: 1.,
shm: winit_state.shm.wl_shm().clone(),
size,
stateless_size: size,
text_inputs: Vec::new(),
theme,
title: String::default(),
transparent: false,
viewport,
window: ManuallyDrop::new(window),
}
}
/// Apply closure on the given pointer.
fn apply_on_poiner<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self,
@@ -163,6 +207,33 @@ impl WindowState {
})
}
/// Get the current state of the frame callback.
pub fn frame_callback_state(&self) -> FrameCallbackState {
self.frame_callback_state
}
/// The frame callback was received, but not yet sent to the user.
pub fn frame_callback_received(&mut self) {
self.frame_callback_state = FrameCallbackState::Received;
}
/// Reset the frame callbacks state.
pub fn frame_callback_reset(&mut self) {
self.frame_callback_state = FrameCallbackState::None;
}
/// Request a frame callback if we don't have one for this window in flight.
pub fn request_frame_callback(&mut self) {
let surface = self.window.wl_surface();
match self.frame_callback_state {
FrameCallbackState::None | FrameCallbackState::Received => {
self.frame_callback_state = FrameCallbackState::Requested;
surface.frame(&self.queue_handle, surface.clone());
}
FrameCallbackState::Requested => (),
}
}
pub fn configure(
&mut self,
configure: WindowConfigure,
@@ -279,7 +350,7 @@ impl WindowState {
FrameAction::Maximize => self.window.set_maximized(),
FrameAction::UnMaximize => self.window.unset_maximized(),
FrameAction::Close => WinitState::queue_close(updates, window_id),
FrameAction::Move => self.window.move_(seat, serial),
FrameAction::Move => self.has_pending_move = Some(serial),
FrameAction::Resize(edge) => self.window.resize(seat, serial, edge),
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
};
@@ -294,9 +365,26 @@ impl WindowState {
}
// Move the point over decorations.
pub fn frame_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str> {
pub fn frame_point_moved(
&mut self,
seat: &WlSeat,
surface: &WlSurface,
x: f64,
y: f64,
) -> Option<&str> {
// Take the serial if we had any, so it doesn't stick around.
let serial = self.has_pending_move.take();
if let Some(frame) = self.frame.as_mut() {
frame.click_point_moved(surface, x, y)
let cursor = frame.click_point_moved(surface, x, y);
// If we have a cursor change, that means that cursor is over the decorations,
// so try to apply move.
if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
self.window.move_(seat, serial);
None
} else {
cursor
}
} else {
None
}
@@ -362,66 +450,13 @@ impl WindowState {
.map(|configure| configure.decoration_mode == DecorationMode::Client)
.unwrap_or(false);
if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
frame.is_hidden()
!frame.is_hidden()
} else {
// Server side decorations.
true
}
}
/// Create new window state.
pub fn new(
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
size: LogicalSize<u32>,
window: Window,
theme: Option<Theme>,
) -> Self {
let compositor = winit_state.compositor_state.clone();
let pointer_constraints = winit_state.pointer_constraints.clone();
let viewport = winit_state
.viewporter_state
.as_ref()
.map(|state| state.get_viewport(window.wl_surface(), queue_handle));
let fractional_scale = winit_state
.fractional_scaling_manager
.as_ref()
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
Self {
compositor,
connection,
theme,
csd_fails: false,
decorate: true,
cursor_grab_mode: GrabState::new(),
cursor_icon: CursorIcon::Default,
cursor_visible: true,
fractional_scale,
frame: None,
has_focus: false,
ime_allowed: false,
ime_purpose: ImePurpose::Normal,
last_configure: None,
max_inner_size: None,
min_inner_size: MIN_WINDOW_SIZE,
pointer_constraints,
pointers: Default::default(),
queue_handle: queue_handle.clone(),
scale_factor: 1.,
shm: winit_state.shm.wl_shm().clone(),
size,
stateless_size: size,
text_inputs: Vec::new(),
title: String::default(),
transparent: false,
resizable: true,
viewport,
window: ManuallyDrop::new(window),
}
}
/// Get the outer size of the window.
#[inline]
pub fn outer_size(&self) -> LogicalSize<u32> {
@@ -677,7 +712,7 @@ impl WindowState {
// Positon can be set only for locked cursor.
if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
return Err(ExternalError::Os(os_error!(
crate::platform_impl::OsError::WaylandMisc(
crate::platform_impl::OsError::Misc(
"cursor position can be set only for locked cursor."
)
)));
@@ -869,6 +904,37 @@ impl Drop for WindowState {
}
}
/// The state of the cursor grabs.
#[derive(Clone, Copy)]
struct GrabState {
/// The grab mode requested by the user.
user_grab_mode: CursorGrabMode,
/// The current grab mode.
current_grab_mode: CursorGrabMode,
}
impl GrabState {
fn new() -> Self {
Self {
user_grab_mode: CursorGrabMode::None,
current_grab_mode: CursorGrabMode::None,
}
}
}
/// The state of the frame callback.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameCallbackState {
/// No frame callback was requsted.
#[default]
None,
/// The frame callback was requested, but not yet arrived, the redraw events are throttled.
Requested,
/// The callback was marked as done, and user could receive redraw requested
Received,
}
impl From<ResizeDirection> for ResizeEdge {
fn from(value: ResizeDirection) -> Self {
match value {

View File

@@ -0,0 +1,200 @@
// SPDX-License-Identifier: Apache-2.0
//! X11 activation handling.
//!
//! X11 has a "startup notification" specification similar to Wayland's, see this URL:
//! <https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt>
use super::{atoms::*, VoidCookie, X11Error, XConnection};
use std::ffi::CString;
use std::fmt::Write;
use x11rb::protocol::xproto::{self, ConnectionExt as _};
impl XConnection {
/// "Request" a new activation token from the server.
pub(crate) fn request_activation_token(&self, window_title: &str) -> Result<String, X11Error> {
// The specification recommends the format "hostname+pid+"_TIME"+current time"
let uname = rustix::system::uname();
let pid = rustix::process::getpid();
let time = self.timestamp();
let activation_token = format!(
"{}{}_TIME{}",
uname.nodename().to_str().unwrap_or("winit"),
pid.as_raw_nonzero(),
time
);
// Set up the new startup notification.
let notification = {
let mut buffer = Vec::new();
buffer.extend_from_slice(b"new: ID=");
quote_string(&activation_token, &mut buffer);
buffer.extend_from_slice(b" NAME=");
quote_string(window_title, &mut buffer);
buffer.extend_from_slice(b" SCREEN=");
push_display(&mut buffer, &self.default_screen_index());
CString::new(buffer)
.map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
.into_bytes_with_nul()
};
self.send_message(&notification)?;
Ok(activation_token)
}
/// Finish launching a window with the given startup ID.
pub(crate) fn remove_activation_token(
&self,
window: xproto::Window,
startup_id: &str,
) -> Result<(), X11Error> {
let atoms = self.atoms();
// Set the _NET_STARTUP_ID property on the window.
self.xcb_connection()
.change_property(
xproto::PropMode::REPLACE,
window,
atoms[_NET_STARTUP_ID],
xproto::AtomEnum::STRING,
8,
startup_id.len().try_into().unwrap(),
startup_id.as_bytes(),
)?
.check()?;
// Send the message indicating that the startup is over.
let message = {
const MESSAGE_ROOT: &str = "remove: ID=";
let mut buffer = Vec::with_capacity(
MESSAGE_ROOT
.len()
.checked_add(startup_id.len())
.and_then(|x| x.checked_add(1))
.unwrap(),
);
buffer.extend_from_slice(MESSAGE_ROOT.as_bytes());
quote_string(startup_id, &mut buffer);
CString::new(buffer)
.map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
.into_bytes_with_nul()
};
self.send_message(&message)
}
/// Send a startup notification message to the window manager.
fn send_message(&self, message: &[u8]) -> Result<(), X11Error> {
let atoms = self.atoms();
// Create a new window to send the message over.
let screen = self.default_root();
let window = xproto::WindowWrapper::create_window(
self.xcb_connection(),
screen.root_depth,
screen.root,
-100,
-100,
1,
1,
0,
xproto::WindowClass::INPUT_OUTPUT,
screen.root_visual,
&xproto::CreateWindowAux::new()
.override_redirect(1)
.event_mask(
xproto::EventMask::STRUCTURE_NOTIFY | xproto::EventMask::PROPERTY_CHANGE,
),
)?;
// Serialize the messages in 20-byte chunks.
let mut message_type = atoms[_NET_STARTUP_INFO_BEGIN];
message
.chunks(20)
.map(|chunk| {
let mut buffer = [0u8; 20];
buffer[..chunk.len()].copy_from_slice(chunk);
let event =
xproto::ClientMessageEvent::new(8, window.window(), message_type, buffer);
// Set the message type to the continuation atom for the next chunk.
message_type = atoms[_NET_STARTUP_INFO];
event
})
.try_for_each(|event| {
// Send each event in order.
self.xcb_connection()
.send_event(
false,
screen.root,
xproto::EventMask::PROPERTY_CHANGE,
event,
)
.map(VoidCookie::ignore_error)
})?;
Ok(())
}
}
/// Quote a literal string as per the startup notification specification.
fn quote_string(s: &str, target: &mut Vec<u8>) {
let total_len = s.len().checked_add(3).expect("quote string overflow");
target.reserve(total_len);
// Add the opening quote.
target.push(b'"');
// Iterate over the string split by literal quotes.
s.as_bytes().split(|&b| b == b'"').for_each(|part| {
// Add the part.
target.extend_from_slice(part);
// Escape the quote.
target.push(b'\\');
target.push(b'"');
});
// Un-escape the last quote.
target.remove(target.len() - 2);
}
/// Push a `Display` implementation to the buffer.
fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
struct Writer<'a> {
buffer: &'a mut Vec<u8>,
}
impl<'a> std::fmt::Write for Writer<'a> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.extend_from_slice(s.as_bytes());
Ok(())
}
}
write!(Writer { buffer }, "{}", display).unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn properly_escapes_x11_messages() {
let assert_eq = |input: &str, output: &[u8]| {
let mut buf = vec![];
quote_string(input, &mut buf);
assert_eq!(buf, output);
};
assert_eq("", b"\"\"");
assert_eq("foo", b"\"foo\"");
assert_eq("foo\"bar", b"\"foo\\\"bar\"");
}
}

View File

@@ -0,0 +1,116 @@
//! Collects every atom used by the platform implementation.
use core::ops::Index;
macro_rules! atom_manager {
($($name:ident $(:$lit:literal)?),*) => {
x11rb::atom_manager! {
/// The atoms used by `winit`
pub(crate) Atoms: AtomsCookie {
$($name $(:$lit)?,)*
}
}
/// Indices into the `Atoms` struct.
#[derive(Copy, Clone, Debug)]
#[allow(non_camel_case_types)]
pub(crate) enum AtomName {
$($name,)*
}
impl AtomName {
pub(crate) fn atom_from(
self,
atoms: &Atoms
) -> &x11rb::protocol::xproto::Atom {
match self {
$(AtomName::$name => &atoms.$name,)*
}
}
}
};
}
atom_manager! {
// General Use Atoms
CARD32,
UTF8_STRING,
WM_CHANGE_STATE,
WM_CLIENT_MACHINE,
WM_DELETE_WINDOW,
WM_PROTOCOLS,
WM_STATE,
XIM_SERVERS,
// Assorted ICCCM Atoms
_NET_WM_ICON,
_NET_WM_MOVERESIZE,
_NET_WM_NAME,
_NET_WM_PID,
_NET_WM_PING,
_NET_WM_STATE,
_NET_WM_STATE_ABOVE,
_NET_WM_STATE_BELOW,
_NET_WM_STATE_FULLSCREEN,
_NET_WM_STATE_HIDDEN,
_NET_WM_STATE_MAXIMIZED_HORZ,
_NET_WM_STATE_MAXIMIZED_VERT,
_NET_WM_WINDOW_TYPE,
// Activation atoms.
_NET_STARTUP_INFO_BEGIN,
_NET_STARTUP_INFO,
_NET_STARTUP_ID,
// WM window types.
_NET_WM_WINDOW_TYPE_DESKTOP,
_NET_WM_WINDOW_TYPE_DOCK,
_NET_WM_WINDOW_TYPE_TOOLBAR,
_NET_WM_WINDOW_TYPE_MENU,
_NET_WM_WINDOW_TYPE_UTILITY,
_NET_WM_WINDOW_TYPE_SPLASH,
_NET_WM_WINDOW_TYPE_DIALOG,
_NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
_NET_WM_WINDOW_TYPE_POPUP_MENU,
_NET_WM_WINDOW_TYPE_TOOLTIP,
_NET_WM_WINDOW_TYPE_NOTIFICATION,
_NET_WM_WINDOW_TYPE_COMBO,
_NET_WM_WINDOW_TYPE_DND,
_NET_WM_WINDOW_TYPE_NORMAL,
// Drag-N-Drop Atoms
XdndAware,
XdndEnter,
XdndLeave,
XdndDrop,
XdndPosition,
XdndStatus,
XdndActionPrivate,
XdndSelection,
XdndFinished,
XdndTypeList,
TextUriList: b"text/uri-list",
None: b"None",
// Miscellaneous Atoms
_GTK_THEME_VARIANT,
_MOTIF_WM_HINTS,
_NET_ACTIVE_WINDOW,
_NET_CLIENT_LIST,
_NET_FRAME_EXTENTS,
_NET_SUPPORTED,
_NET_SUPPORTING_WM_CHECK,
_XEMBED
}
impl Index<AtomName> for Atoms {
type Output = x11rb::protocol::xproto::Atom;
fn index(&self, index: AtomName) -> &Self::Output {
index.atom_from(self)
}
}
pub(crate) use AtomName::*;
// Make sure `None` is still defined.
pub(crate) use core::option::Option::None;

View File

@@ -7,55 +7,12 @@ use std::{
};
use percent_encoding::percent_decode;
use x11rb::protocol::xproto::{self, ConnectionExt};
use super::{ffi, util, XConnection, XError};
#[derive(Debug)]
pub(crate) struct DndAtoms {
pub enter: ffi::Atom,
pub leave: ffi::Atom,
pub drop: ffi::Atom,
pub position: ffi::Atom,
pub status: ffi::Atom,
pub action_private: ffi::Atom,
pub selection: ffi::Atom,
pub finished: ffi::Atom,
pub type_list: ffi::Atom,
pub uri_list: ffi::Atom,
pub none: ffi::Atom,
}
impl DndAtoms {
pub fn new(xconn: &Arc<XConnection>) -> Result<Self, XError> {
let names = [
b"XdndEnter\0".as_ptr() as *mut c_char,
b"XdndLeave\0".as_ptr() as *mut c_char,
b"XdndDrop\0".as_ptr() as *mut c_char,
b"XdndPosition\0".as_ptr() as *mut c_char,
b"XdndStatus\0".as_ptr() as *mut c_char,
b"XdndActionPrivate\0".as_ptr() as *mut c_char,
b"XdndSelection\0".as_ptr() as *mut c_char,
b"XdndFinished\0".as_ptr() as *mut c_char,
b"XdndTypeList\0".as_ptr() as *mut c_char,
b"text/uri-list\0".as_ptr() as *mut c_char,
b"None\0".as_ptr() as *mut c_char,
];
let atoms = unsafe { xconn.get_atoms(&names) }?;
Ok(DndAtoms {
enter: atoms[0],
leave: atoms[1],
drop: atoms[2],
position: atoms[3],
status: atoms[4],
action_private: atoms[5],
selection: atoms[6],
finished: atoms[7],
type_list: atoms[8],
uri_list: atoms[9],
none: atoms[10],
})
}
}
use super::{
atoms::{AtomName::None as DndNone, *},
util, CookieResultExt, X11Error, XConnection,
};
#[derive(Debug, Clone, Copy)]
pub enum DndState {
@@ -86,22 +43,19 @@ impl From<io::Error> for DndDataParseError {
pub(crate) struct Dnd {
xconn: Arc<XConnection>,
pub atoms: DndAtoms,
// Populated by XdndEnter event handler
pub version: Option<c_long>,
pub type_list: Option<Vec<c_ulong>>,
pub type_list: Option<Vec<xproto::Atom>>,
// Populated by XdndPosition event handler
pub source_window: Option<c_ulong>,
pub source_window: Option<xproto::Window>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
}
impl Dnd {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, XError> {
let atoms = DndAtoms::new(&xconn)?;
pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
Ok(Dnd {
xconn,
atoms,
version: None,
type_list: None,
source_window: None,
@@ -118,71 +72,85 @@ impl Dnd {
pub unsafe fn send_status(
&self,
this_window: c_ulong,
target_window: c_ulong,
this_window: xproto::Window,
target_window: xproto::Window,
state: DndState,
) -> Result<(), XError> {
) -> Result<(), X11Error> {
let atoms = self.xconn.atoms();
let (accepted, action) = match state {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
DndState::Accepted => (1, atoms[XdndActionPrivate]),
DndState::Rejected => (0, atoms[DndNone]),
};
self.xconn
.send_client_msg(
target_window,
target_window,
self.atoms.status,
atoms[XdndStatus] as _,
None,
[this_window as c_long, accepted, 0, 0, action],
)
.flush()
[this_window, accepted, 0, 0, action as _],
)?
.ignore_error();
Ok(())
}
pub unsafe fn send_finished(
&self,
this_window: c_ulong,
target_window: c_ulong,
this_window: xproto::Window,
target_window: xproto::Window,
state: DndState,
) -> Result<(), XError> {
) -> Result<(), X11Error> {
let atoms = self.xconn.atoms();
let (accepted, action) = match state {
DndState::Accepted => (1, self.atoms.action_private as c_long),
DndState::Rejected => (0, self.atoms.none as c_long),
DndState::Accepted => (1, atoms[XdndActionPrivate]),
DndState::Rejected => (0, atoms[DndNone]),
};
self.xconn
.send_client_msg(
target_window,
target_window,
self.atoms.finished,
atoms[XdndFinished] as _,
None,
[this_window as c_long, accepted, action, 0, 0],
)
.flush()
[this_window, accepted, action as _, 0, 0],
)?
.ignore_error();
Ok(())
}
pub unsafe fn get_type_list(
&self,
source_window: c_ulong,
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
self.xconn
.get_property(source_window, self.atoms.type_list, ffi::XA_ATOM)
source_window: xproto::Window,
) -> Result<Vec<xproto::Atom>, util::GetPropertyError> {
let atoms = self.xconn.atoms();
self.xconn.get_property(
source_window,
atoms[XdndTypeList],
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
}
pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) {
(self.xconn.xlib.XConvertSelection)(
self.xconn.display,
self.atoms.selection,
self.atoms.uri_list,
self.atoms.selection,
window,
time,
);
pub unsafe fn convert_selection(&self, window: xproto::Window, time: xproto::Timestamp) {
let atoms = self.xconn.atoms();
self.xconn
.xcb_connection()
.convert_selection(
window,
atoms[XdndSelection],
atoms[TextUriList],
atoms[XdndSelection],
time,
)
.expect_then_ignore_error("Failed to send XdndSelection event")
}
pub unsafe fn read_data(
&self,
window: c_ulong,
window: xproto::Window,
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
let atoms = self.xconn.atoms();
self.xconn
.get_property(window, self.atoms.selection, self.atoms.uri_list)
.get_property(window, atoms[XdndSelection], atoms[TextUriList])
}
pub fn parse_data(&self, data: &mut [c_uchar]) -> Result<Vec<PathBuf>, DndDataParseError> {

View File

@@ -1,13 +1,21 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
use libc::{c_char, c_int, c_long, c_ulong};
use super::{
ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, DndState,
GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, XExtension,
use std::{
cell::RefCell,
collections::HashMap,
os::raw::{c_char, c_int, c_long, c_ulong},
rc::Rc,
slice,
sync::{Arc, Mutex},
};
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::Serialize;
use super::{
atoms::*, ffi, get_xtarget, mkdid, mkwid, monitor, util, CookieResultExt, Device, DeviceId,
DeviceInfo, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow,
WindowId, XExtension,
};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event::{DeviceEvent, ElementState, Event, Ime, RawKeyEvent, TouchPhase, WindowEvent},
@@ -15,6 +23,10 @@ use crate::{
keyboard::ModifiersState,
platform_impl::platform::common::{keymap, xkb_state::KbdState},
};
use crate::{
event::InnerSizeWriter,
platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest},
};
/// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`".
const KEYCODE_OFFSET: u8 = 8;
@@ -31,11 +43,14 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) kb_state: KbdState,
// Number of touch events currently in progress
pub(super) num_touch: u32,
// Whether we've got a key release for the key press.
pub(super) got_key_release: bool,
// This is the last pressed key that is repeatable (if it hasn't been
// released).
//
// Used to detect key repeats.
pub(super) held_key_press: Option<u32>,
pub(super) first_touch: Option<u64>,
// Currently focused window belonging to this process
pub(super) active_window: Option<ffi::Window>,
pub(super) active_window: Option<xproto::Window>,
pub(super) is_composing: bool,
}
@@ -50,7 +65,7 @@ impl<T: 'static> EventProcessor<T> {
}
}
fn with_window<F, Ret>(&self, window_id: ffi::Window, callback: F) -> Option<Ret>
pub(crate) fn with_window<F, Ret>(&self, window_id: xproto::Window, callback: F) -> Option<Ret>
where
F: Fn(&Arc<UnownedWindow>) -> Ret,
{
@@ -74,7 +89,7 @@ impl<T: 'static> EventProcessor<T> {
result
}
fn window_exists(&self, window_id: ffi::Window) -> bool {
fn window_exists(&self, window_id: xproto::Window) -> bool {
self.with_window(window_id, |_| ()).is_some()
}
@@ -114,9 +129,10 @@ impl<T: 'static> EventProcessor<T> {
pub(super) fn process_event<F>(&mut self, xev: &mut ffi::XEvent, mut callback: F)
where
F: FnMut(Event<'_, T>),
F: FnMut(Event<T>),
{
let wt = get_xtarget(&self.target);
let atoms = wt.x_connection().atoms();
// XFilterEvent tells us when an event has been discarded by the input method.
// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
// along with an extra copy of the KeyRelease events. This also prevents backspace and
@@ -137,42 +153,57 @@ impl<T: 'static> EventProcessor<T> {
ffi::ClientMessage => {
let client_msg: &ffi::XClientMessageEvent = xev.as_ref();
let window = client_msg.window;
let window = client_msg.window as xproto::Window;
let window_id = mkwid(window);
if client_msg.data.get_long(0) as ffi::Atom == wt.wm_delete_window {
if client_msg.data.get_long(0) as xproto::Atom == wt.wm_delete_window {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::CloseRequested,
});
} else if client_msg.data.get_long(0) as ffi::Atom == wt.net_wm_ping {
} else if client_msg.data.get_long(0) as xproto::Atom == wt.net_wm_ping {
let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut();
response_msg.window = wt.root;
let client_msg = xproto::ClientMessageEvent {
response_type: xproto::CLIENT_MESSAGE_EVENT,
format: response_msg.format as _,
sequence: response_msg.serial as _,
window: wt.root,
type_: response_msg.message_type as _,
data: xproto::ClientMessageData::from({
let [a, b, c, d, e]: [c_long; 5] =
response_msg.data.as_longs().try_into().unwrap();
[a as u32, b as u32, c as u32, d as u32, e as u32]
}),
};
wt.xconn
.xcb_connection()
.send_event(
false,
wt.root,
Some(ffi::SubstructureNotifyMask | ffi::SubstructureRedirectMask),
*response_msg,
xproto::EventMask::SUBSTRUCTURE_NOTIFY
| xproto::EventMask::SUBSTRUCTURE_REDIRECT,
client_msg.serialize(),
)
.queue();
} else if client_msg.message_type == self.dnd.atoms.enter {
let source_window = client_msg.data.get_long(0) as c_ulong;
.expect_then_ignore_error("Failed to send `ClientMessage` event.");
} else if client_msg.message_type == atoms[XdndEnter] as c_ulong {
let source_window = client_msg.data.get_long(0) as xproto::Window;
let flags = client_msg.data.get_long(1);
let version = flags >> 24;
self.dnd.version = Some(version);
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
if !has_more_types {
let type_list = vec![
client_msg.data.get_long(2) as c_ulong,
client_msg.data.get_long(3) as c_ulong,
client_msg.data.get_long(4) as c_ulong,
client_msg.data.get_long(2) as xproto::Atom,
client_msg.data.get_long(3) as xproto::Atom,
client_msg.data.get_long(4) as xproto::Atom,
];
self.dnd.type_list = Some(type_list);
} else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) }
{
self.dnd.type_list = Some(more_types);
}
} else if client_msg.message_type == self.dnd.atoms.position {
} else if client_msg.message_type == atoms[XdndPosition] as c_ulong {
// This event occurs every time the mouse moves while a file's being dragged
// over our window. We emit HoveredFile in response; while the macOS backend
// does that upon a drag entering, XDND doesn't have access to the actual drop
@@ -181,7 +212,7 @@ impl<T: 'static> EventProcessor<T> {
// supply position updates with `HoveredFile` or another event, implementing
// that here would be trivial.
let source_window = client_msg.data.get_long(0) as c_ulong;
let source_window = client_msg.data.get_long(0) as xproto::Window;
// Equivalent to `(x << shift) | y`
// where `shift = mem::size_of::<c_short>() * 8`
@@ -199,7 +230,7 @@ impl<T: 'static> EventProcessor<T> {
//let action = client_msg.data.get_long(4);
let accepted = if let Some(ref type_list) = self.dnd.type_list {
type_list.contains(&self.dnd.atoms.uri_list)
type_list.contains(&atoms[TextUriList])
} else {
false
};
@@ -209,11 +240,15 @@ impl<T: 'static> EventProcessor<T> {
unsafe {
if self.dnd.result.is_none() {
let time = if version >= 1 {
client_msg.data.get_long(3) as c_ulong
client_msg.data.get_long(3) as xproto::Timestamp
} else {
// In version 0, time isn't specified
ffi::CurrentTime
x11rb::CURRENT_TIME
};
// Log this timestamp.
wt.xconn.set_timestamp(time);
// This results in the `SelectionNotify` event below
self.dnd.convert_selection(window, time);
}
@@ -229,7 +264,7 @@ impl<T: 'static> EventProcessor<T> {
}
self.dnd.reset();
}
} else if client_msg.message_type == self.dnd.atoms.drop {
} else if client_msg.message_type == atoms[XdndDrop] as c_ulong {
let (source_window, state) = if let Some(source_window) = self.dnd.source_window
{
if let Some(Ok(ref path_list)) = self.dnd.result {
@@ -244,7 +279,7 @@ impl<T: 'static> EventProcessor<T> {
} else {
// `source_window` won't be part of our DND state if we already rejected the drop in our
// `XdndPosition` handler.
let source_window = client_msg.data.get_long(0) as c_ulong;
let source_window = client_msg.data.get_long(0) as xproto::Window;
(source_window, DndState::Rejected)
};
unsafe {
@@ -253,7 +288,7 @@ impl<T: 'static> EventProcessor<T> {
.expect("Failed to send `XdndFinished` message.");
}
self.dnd.reset();
} else if client_msg.message_type == self.dnd.atoms.leave {
} else if client_msg.message_type == atoms[XdndLeave] as c_ulong {
self.dnd.reset();
callback(Event::WindowEvent {
window_id,
@@ -265,10 +300,13 @@ impl<T: 'static> EventProcessor<T> {
ffi::SelectionNotify => {
let xsel: &ffi::XSelectionEvent = xev.as_ref();
let window = xsel.requestor;
let window = xsel.requestor as xproto::Window;
let window_id = mkwid(window);
if xsel.property == self.dnd.atoms.selection {
// Set the timestamp.
wt.xconn.set_timestamp(xsel.time as xproto::Timestamp);
if xsel.property == atoms[XdndSelection] as c_ulong {
let mut result = None;
// This is where we receive data from drag and drop
@@ -291,7 +329,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::ConfigureNotify => {
let xev: &ffi::XConfigureEvent = xev.as_ref();
let xwindow = xev.window;
let xwindow = xev.window as xproto::Window;
let window_id = mkwid(xwindow);
if let Some(window) = self.with_window(xwindow, Arc::clone) {
@@ -402,21 +440,27 @@ impl<T: 'static> EventProcessor<T> {
);
let old_inner_size = PhysicalSize::new(width, height);
let mut new_inner_size = PhysicalSize::new(new_width, new_height);
let new_inner_size = PhysicalSize::new(new_width, new_height);
// Unlock shared state to prevent deadlock in callback below
drop(shared_state_lock);
let inner_size = Arc::new(Mutex::new(new_inner_size));
callback(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor,
new_inner_size: &mut new_inner_size,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&inner_size,
)),
},
});
let new_inner_size = *inner_size.lock().unwrap();
drop(inner_size);
if new_inner_size != old_inner_size {
window.set_inner_size_physical(
window.request_inner_size_physical(
new_inner_size.width,
new_inner_size.height,
);
@@ -440,7 +484,7 @@ impl<T: 'static> EventProcessor<T> {
// When this finally happens, the event will not be synthetic.
shared_state_lock.dpi_adjusted = None;
} else {
window.set_inner_size_physical(adjusted_size.0, adjusted_size.1);
window.request_inner_size_physical(adjusted_size.0, adjusted_size.1);
}
}
@@ -466,13 +510,13 @@ impl<T: 'static> EventProcessor<T> {
// effect is that we waste some time trying to query unsupported properties.
wt.xconn.update_cached_wm_info(wt.root);
self.with_window(xev.window, |window| {
self.with_window(xev.window as xproto::Window, |window| {
window.invalidate_cached_frame_extents();
});
}
ffi::MapNotify => {
let xev: &ffi::XMapEvent = xev.as_ref();
let window = xev.window;
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
// XXX re-issue the focus state when mapping the window.
@@ -491,7 +535,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::DestroyNotify => {
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
let window = xev.window;
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
// In the event that the window's been destroyed without being dropped first, we
@@ -502,7 +546,7 @@ impl<T: 'static> EventProcessor<T> {
// context here instead of when dropping the window.
wt.ime
.borrow_mut()
.remove_context(window)
.remove_context(window as ffi::Window)
.expect("Failed to destroy input context");
callback(Event::WindowEvent {
@@ -513,7 +557,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::VisibilityNotify => {
let xev: &ffi::XVisibilityEvent = xev.as_ref();
let xwindow = xev.window;
let xwindow = xev.window as xproto::Window;
callback(Event::WindowEvent {
window_id: mkwid(xwindow),
event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured),
@@ -529,7 +573,7 @@ impl<T: 'static> EventProcessor<T> {
// Multiple Expose events may be received for subareas of a window.
// We issue `RedrawRequested` only for the last event of such a series.
if xev.count == 0 {
let window = xev.window;
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
callback(Event::RedrawRequested(window_id));
@@ -539,22 +583,52 @@ impl<T: 'static> EventProcessor<T> {
// Note that in compose/pre-edit sequences, we'll always receive KeyRelease events
ty @ ffi::KeyPress | ty @ ffi::KeyRelease => {
let xkev: &mut ffi::XKeyEvent = xev.as_mut();
// Set the timestamp.
wt.xconn.set_timestamp(xkev.time as xproto::Timestamp);
let window = match self.active_window {
Some(window) => window,
None => return,
};
let window_id = mkwid(window);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
let keycode = xkev.keycode as _;
let repeat = ty == ffi::KeyPress && !self.got_key_release;
// Update state after the repeat setting.
// Update state to track key repeats and determine whether this key was a repeat.
//
// Note, when a key is held before focusing on this window the first
// (non-synthetic) event will not be flagged as a repeat (also note that the
// synthetic press event that is generated before this when the window gains focus
// will also not be flagged as a repeat).
//
// Only keys that can repeat should change the held_key_press state since a
// continuously held repeatable key may continue repeating after the press of a
// non-repeatable key.
let repeat = if self.kb_state.key_repeats(keycode) {
let is_latest_held = self.held_key_press == Some(keycode);
if ty == ffi::KeyPress {
self.held_key_press = Some(keycode);
is_latest_held
} else {
// Check that the released key is the latest repeatable key that has been
// pressed, since repeats will continue for the latest key press if a
// different previously pressed key is released.
if is_latest_held {
self.held_key_press = None;
}
false
}
} else {
false
};
let state = if ty == ffi::KeyPress {
self.got_key_release = false;
ElementState::Pressed
} else {
self.got_key_release = true;
ElementState::Released
};
@@ -568,7 +642,7 @@ impl<T: 'static> EventProcessor<T> {
is_synthetic: false,
},
});
} else if let Some(ic) = wt.ime.borrow().get_context(window) {
} else if let Some(ic) = wt.ime.borrow().get_context(window as ffi::Window) {
let written = wt.xconn.lookup_utf8(ic, xkev);
if !written.is_empty() {
let event = Event::WindowEvent {
@@ -613,8 +687,12 @@ impl<T: 'static> EventProcessor<T> {
match xev.evtype {
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
let window_id = mkwid(xev.event as xproto::Window);
let device_id = mkdid(xev.deviceid);
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
if (xev.flags & ffi::XIPointerEmulated) != 0 {
// Deliver multi-touch events instead of emulated mouse events.
return;
@@ -702,11 +780,16 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::XI_Motion => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let device_id = mkdid(xev.deviceid);
let window_id = mkwid(xev.event);
let window = xev.event as xproto::Window;
let window_id = mkwid(window);
let new_cursor_pos = (xev.event_x, xev.event_y);
let cursor_moved = self.with_window(xev.event, |window| {
let cursor_moved = self.with_window(window, |window| {
let mut shared_state_lock = window.shared_state_lock();
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
});
@@ -788,7 +871,11 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_Enter => {
let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let window = xev.event as xproto::Window;
let window_id = mkwid(window);
let device_id = mkdid(xev.deviceid);
if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) {
@@ -809,7 +896,7 @@ impl<T: 'static> EventProcessor<T> {
}
}
if self.window_exists(xev.event) {
if self.window_exists(window) {
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
@@ -828,13 +915,17 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::XI_Leave => {
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
let window = xev.event as xproto::Window;
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
// Leave, FocusIn, and FocusOut can be received by a window that's already
// been destroyed, which the user presumably doesn't want to deal with.
let window_closed = !self.window_exists(xev.event);
let window_closed = !self.window_exists(window);
if !window_closed {
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
window_id: mkwid(window),
event: CursorLeft {
device_id: mkdid(xev.deviceid),
},
@@ -843,21 +934,25 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
let window = xev.event as xproto::Window;
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
wt.ime
.borrow_mut()
.focus(xev.event)
.expect("Failed to focus input context");
if self.active_window != Some(xev.event) {
self.active_window = Some(xev.event);
if self.active_window != Some(window) {
self.active_window = Some(window);
wt.update_listen_device_events(true);
let window_id = mkwid(xev.event);
let window_id = mkwid(window);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
if let Some(window) = self.with_window(xev.event, Arc::clone) {
if let Some(window) = self.with_window(window, Arc::clone) {
window.shared_state_lock().has_focus = true;
}
@@ -904,7 +999,12 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
if !self.window_exists(xev.event) {
let window = xev.event as xproto::Window;
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
if !self.window_exists(window) {
return;
}
@@ -913,8 +1013,8 @@ impl<T: 'static> EventProcessor<T> {
.unfocus(xev.event)
.expect("Failed to unfocus input context");
if self.active_window.take() == Some(xev.event) {
let window_id = mkwid(xev.event);
if self.active_window.take() == Some(window) {
let window_id = mkwid(window);
wt.update_listen_device_events(false);
@@ -926,6 +1026,9 @@ impl<T: 'static> EventProcessor<T> {
&mut self.kb_state,
&mut callback,
);
// Clear this so detecting key repeats is consistently handled when the
// window regains focus.
self.held_key_press = None;
callback(Event::WindowEvent {
window_id,
@@ -934,7 +1037,7 @@ impl<T: 'static> EventProcessor<T> {
),
});
if let Some(window) = self.with_window(xev.event, Arc::clone) {
if let Some(window) = self.with_window(window, Arc::clone) {
window.shared_state_lock().has_focus = false;
}
@@ -947,14 +1050,19 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let window = xev.event as xproto::Window;
let window_id = mkwid(window);
let phase = match xev.evtype {
ffi::XI_TouchBegin => TouchPhase::Started,
ffi::XI_TouchUpdate => TouchPhase::Moved,
ffi::XI_TouchEnd => TouchPhase::Ended,
_ => unreachable!(),
};
if self.window_exists(xev.event) {
if self.window_exists(window) {
let id = xev.detail as u64;
let location = PhysicalPosition::new(xev.event_x, xev.event_y);
@@ -965,7 +1073,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved {
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
device_id: mkdid(util::VIRTUAL_CORE_POINTER.into()),
position: location.cast(),
},
});
@@ -986,6 +1094,10 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => {
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
if xev.flags & ffi::XIPointerEmulated == 0 {
callback(Event::DeviceEvent {
device_id: mkdid(xev.deviceid),
@@ -1003,6 +1115,10 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_RawMotion => {
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let did = mkdid(xev.deviceid);
let mask = unsafe {
@@ -1054,6 +1170,9 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => {
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let state = match xev.evtype {
ffi::XI_RawKeyPress => Pressed,
ffi::XI_RawKeyRelease => Released,
@@ -1078,6 +1197,10 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_HierarchyChanged => {
let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) };
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
for info in
unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }
{
@@ -1110,6 +1233,10 @@ impl<T: 'static> EventProcessor<T> {
let xev = unsafe {
&*(xev as *const _ as *const ffi::XkbNewKeyboardNotifyEvent)
};
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let keycodes_changed_flag = 0x1;
let geometry_changed_flag = 0x1 << 1;
@@ -1128,6 +1255,9 @@ impl<T: 'static> EventProcessor<T> {
let xev =
unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) };
// Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
let prev_mods = self.kb_state.mods_state();
self.kb_state.update_modifiers(
xev.base_mods,
@@ -1187,21 +1317,27 @@ impl<T: 'static> EventProcessor<T> {
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);
let inner_size = Arc::new(Mutex::new(
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,
inner_size_writer: InnerSizeWriter::new(
Arc::downgrade(&inner_size),
),
},
});
let new_inner_size = *inner_size.lock().unwrap();
drop(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);
window.request_inner_size_physical(
new_width, new_height,
);
}
}
}
@@ -1227,7 +1363,7 @@ impl<T: 'static> EventProcessor<T> {
}
let (window, event) = match self.ime_event_receiver.try_recv() {
Ok((window, event)) => (window, event),
Ok((window, event)) => (window as xproto::Window, event),
Err(_) => return,
};
@@ -1278,9 +1414,9 @@ impl<T: 'static> EventProcessor<T> {
kb_state: &mut KbdState,
callback: &mut F,
) where
F: FnMut(Event<'_, T>),
F: FnMut(Event<T>),
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
// Update modifiers state and emit key events based on which keys are currently pressed.
for keycode in wt

File diff suppressed because it is too large Load Diff

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